feat: Raven gets a window, and the audit that came with it - #5
Merged
Conversation
The architecture reserved crates/raven-gui behind a trigger that is now met. Nothing about the raven crate changes: the same sources, the same tests, the same seven suites passing. The workspace root carries the shared version and the release profile so the two crates cannot drift apart.
colony-ui supplies the palette and the type scale. Every size goes through Typography::sz from the first line, because the layout has to survive the 0.7225x-1.68x the org's two font preferences produce when multiplied, and retrofitting that later means touching every call site. No assets/fonts/ directory exists yet, so the window relies on JetBrainsMono Nerd Font being installed system-wide rather than bundling it; a later task can add the bundled font deliberately. Two deviations from the task brief's code, both required to compile/pass lint under iced 0.14 and this repo's -D warnings clippy config: - Column::spacing needs impl Into<Pixels>, which is not implemented for u16, only f32/u32 - pass t.sz(8) directly instead of casting to u16. - theme.rs's doc comment used a Markdown-list-shaped '- X - Y' construction that clippy::doc_lazy_continuation flags as a malformed list; reworded without changing its meaning.
Reading one environment opens /proc for every process on the machine, so no library call runs where it could stutter the window. The model is plain data with its own tests - status_line included, because '1 processes' is the kind of thing a widget tree test would never catch.
Starting is the most frequent action and env start is what makes launches instant, so it does not hide behind a detail view.
Raven's messages are written for a terminal and several end with a command to run. In a window that is an admission of failure, so the ones with an obvious action become a button. The rest keep their own words - the text is good, and a paraphrase would lose the part that explains why.
Install needs a file and this batch has no file dialog, so the button says which command takes one rather than pretending. That is a dependency decision worth making deliberately, not while building a screen.
…gress The only long operation in Raven, and minutes of silence in the CLI. The GUI spawns the CLI and parses wimlib's own output - no library change was needed, because .status() inherits the parent's stdio and wimlib does not suppress progress when piped. Both format strings are pinned by a test against output captured from a real apply.
A review of the deployment progress bar flagged Command::new("raven") as
undocumented: under cargo run it silently runs the system's installed CLI
rather than the workspace build, with no hint that happened. PATH is still
the right choice - the package installs the GUI and CLI together at the same
version - so this documents the reasoning on pump() rather than changing the
resolution strategy.
raven doctor says 'absent - Wine falls back to wineserver' rather than 'no', and a green tick would throw away the only useful half of that.
Two binaries, two desktop entries, one version. They are separate entries on purpose: raven.desktop carries the .exe association, and a double-clicked program must run rather than open the manager.
Eleven of the thirteen buttons carried no `.style()`, which does not mean "no style" - it means iced's built-in theme, so they rendered iced's blue on colony-ui's ground. `btn_default`, `btn_hover` and `btn_pressed` were unused in the whole crate: the palette carried three button colours that nothing asked for. One helper in `theme.rs` rather than a copy per call site, so the next button added cannot quietly opt out of the palette by saying nothing. It covers `Disabled` too - the Deploy button spends minutes in that state during a deployment, and the built-in theme's disabled fill was as foreign as its active one. The environment name on a card keeps its own style: it is the card's heading and only behaves like a button. Its colour is now stated rather than left to `button::Style::default()`, whose text is black.
Two halves of the same seam, written by different tasks and reviewed apart. `Acted(Err)` set the offer and then returned `load::environments()`, and `Environments(Ok)` cleared the offer. So the flagship behaviour the design asks for - an error that arrives with a *Stop the session* button - appeared for the length of a `/proc` scan and then erased itself. The clear is now narrowed to what a load raised: a successful read says the read works again, and says nothing about the action that failed. `Offer` carries a `Kind` to tell the two apart, which is also what lets guidance stop being painted as a failure - pressing "Install…" was rendering instructions in `error` ink on `error_bg`. And the design's Data flow section asks for a refresh on actions plus a two-second poll while the environments screen is open. None of it was built: `Message::Refresh` was declared and never constructed, and `Go(Environments)` returned no task, so the home screen showed whatever was true at launch until an action happened to reload it. The poll is `iced::time::every`, subscribed only while that screen or a detail is showing - reading every process on the machine for a screen nobody is looking at is the waste the design refuses - and `loading_envs` drops a tick that arrives while the last scan is still running rather than queueing a second one behind it. Navigating now clears the banner too. An error raised on one screen said nothing about another and made it look broken.
…binds iced defaults `application_id` to the empty string. A Wayland compositor has no other way to tell which desktop file a running window came from - it matches that id against a desktop file's basename - so nothing would ever match `raven-gui.desktop`, and the `Icon=raven` that docs/internals/packaging.md promises would never reach a taskbar. The menu entry would carry the mark and the window it launched would show a blank fallback, with nothing to say the two were one program. assets/brand/README.md already writes down the agreement the icon needs; this is a fourth term in it, so it is recorded there rather than left to be rediscovered.
…ropped Two faults in the same 40 lines, and both only bite on the one operation that costs minutes. stderr was inherited. A GUI started from an application menu has nowhere to inherit it to, so the reason a deployment failed went to no terminal at all and the window could only say "run the same command from a terminal to see why" - the admission of failure the design's error section forbids. It is piped now, drained on its own thread so a full stderr pipe cannot deadlock a reader parked on stdout, and the last line that says anything is what the banner shows. The child failing to spawn at all now carries its reason too, instead of being a bare `false`. And `DeployDone` was sent with `try_send` into a sixteen-slot channel, like the progress updates around it. Losing a progress update is right - they arrive faster than the window redraws and the next one is truer. Losing the terminal message left `deploying` and `deploy_job` `Some` for ever: a bar frozen at whatever percentage it reached, the fields and the Deploy button disabled, and no cancel. It leaves the blocking worker as a return value now and is awaited on the async side, so it waits for room rather than giving up. The progress updates stay lossy on purpose.
The diagnostics test built a `Check` literal and then asserted against the string it had just typed. No production code ran, so it would have passed with `checks()` deleted. It calls `checks()` now: which judgements pass differs between machines and cannot be asserted on, but the shape can - four of them, each with a label, none reduced to the bare verdict this screen exists to avoid. The release workflow's build job was still called "Build raven-linux" though it stages two binaries. And `[profile.release]`'s comment still justified `lto = "fat"` with "raven runs once per .exe launch". That is still true of `raven` and now says nothing about `raven-gui`, which the same profile also builds. The reasoning is rewritten to say what is actually true - the settings are kept for the launcher and paid for in the window's compile time - as the design asked. The values are unchanged.
…anifest The workspace conversion moved the version into [workspace.package] and left no [package] at the root. release-please's `rust` strategy assumes a Cargo package lives at the package path and throws when it does not - so no release pull request would have been opened at all, silently, and 0.2.0 would have been the last version this project ever cut. Found by the final whole-branch review, whose premise was that the strategy would no-op; the fix investigation disproved that and found it throws, which is worse and quieter. The fix is not invented here. Eidos is the same shape - a virtual workspace with a GUI crate, one inherited version, CI building with --locked - and solved this already: `release-type: simple` naming the version line through extra-files, plus a step that runs `cargo update --workspace` on the release branch so the lock does not lag the manifest and break the --locked build on merge. Both carry Eidos's comments, including the mistake it already made: conditioning that step on `prs_created` silently skips it, and a step that skips looks exactly like a step that had nothing to do. One deliberate difference. Eidos names the release branch outright; this matches by prefix, because the branch carried `--components--raven` under the `rust` strategy and `simple` has no package name to derive a component from. Guessing that suffix wrong would make the step a no-op in the one case it exists for. More than one open release branch fails loudly rather than picking one.
`Environment::stop()` signals every process holding the mount, SIGTERM then SIGKILL, and does not ask who they are. The banner for `EnvironmentBusy` - raised precisely when something other than Raven's own anchor holds the environment, a game typically - offered that under the label "Stop the session", in a window whose own vocabulary uses "session" for the anchor. One click on a harmless-looking button ended the game. `Action` now tells the two apart: `StopSession` for the anchor, `StopHolders` for everything else, each with its own words, and the `EnvironmentBusy` message says that stopping ends the programs it lists. Both still stop the environment - that is the remedy in both cases - only the label's honesty changed. The labels live on `Action` rather than in the view so the test that pins the distinction can read them. The card's own Stop is unchanged: a count cannot tell a game from Wine's services, and listing the holders there is the spec's own item for the Session section, left for after the visual check.
The sidebar highlighted an entry only when the screen equalled it, and `Screen::Detail` equals none of the three, so opening a card - the most common navigation in the window - turned the whole sidebar dark. Everywhere else the detail is already Environments one level down: `Open` is `Go(Detail)`, the back button returns there, the poll runs for both. The sidebar now agrees.
To count environments per base, `load::bases` built the full environment rows, and each of those reads the session, the holders, the Direct3D manifests and the attachments - where reading the holders walks `/proc/<pid>/mountinfo` for every process on the machine, and the session read does it a second time. Measured at 60 to 70 ms per environment on a 435-process machine, all of it for a comparison of base ids the manifests already hold. `base_rows` now takes those ids and nothing else. `load::bases` no longer touches `/proc`, and the note in `model.rs` that `load::environments` is `env_rows`' only caller has become true.
Start, Stop, Remove, Detach and Reproject each spelled out the same wrapper - open the environment on a blocking thread, one library call, the `Arc` bridge for an error that cannot be `Clone` - five times over: 65 lines for five calls. `act` says it once, so a sixth action cannot quietly do any part of it differently. No behaviour changes; the five closures keep exactly the calls the arms made. `load`'s module doc claimed every library call went through it, which was already untrue of those five arms. It now says what is true: every read does, and actions take the same route through `act`.
The test kept a `.last()` under a clippy allow, with a comment citing "the brief" as the reason - a build-time document no reader of the repository can find. The code under test already uses `.next_back()`, the same operation on a double-ended iterator. The allow was the only one in the repository, and it guarded nothing worth keeping.
`Environment::ensure_session` starts the anchor as `current_exe() session-anchor <name>` - whichever binary asked. Only the launcher answered to that argument, so Start from the window spawned a second `raven-gui`, which opened a window, said nothing on the pipe, and was killed thirty seconds later with a banner blaming the kernel. The GUI's headline action could never have succeeded, and the visual check would have met an unexpected window first. The anchor routine moves from the launcher into the library, beside the code that spawns it, and both binaries call it. The window checks its arguments before iced or tokio exist: the anchor's `unshare(CLONE_NEWUSER)` is refused to a process that already has threads. One behaviour of the routine changed in the move - a failure to record the session file now leaves through stdout like every other failure, instead of through an `anyhow` context on a stderr the anchor does not have. The test runs the window binary with its environment cleared, so no display exists for it to open anything on, and expects the anchor protocol's answer on stdout. Before the fix it printed nothing and died trying to create an event loop.
Two places decided whether an environment was held by Raven's own session or by something else with `comm == "raven"`. That held while only the launcher could start a session. The window can now, and its anchor is the window's binary - `current_exe()` - so it is called `raven-gui`, and both tests classified the window's own session as a foreign program: "still in use by 1234 (raven-gui)", with the button that ends the user's programs offered to end it. A holder is now an anchor when its `/proc/<pid>/cmdline` says so - the first argument is `session-anchor`, whichever binary it is - and `env status` names it among the holders. The rule lives once, on `Holder`, and a test pins it to both binary names and to the things that are not it.
The library re-runs a Raven binary twice: `session-anchor` to hold a session open, and `exec` to mount for a registry import. Both ran `current_exe()`, which is right for the launcher and wrong for the window. The anchor was taught to the window last time; `exec` was not, so the Reproject button launched `raven-gui exec ...`, which fell through to iced and opened a second, unexplained window on the user's screen. Closing that window exits zero, so the import would then report success having projected nothing. Both re-invocations now run the `raven` beside us when there is one - the package installs both binaries into /usr/bin and a cargo build puts both in target/<profile>, so there always is - and the window answers a command line instead of opening for it. An argument it does not know is refused on stdout, the stream the library reads, so a fallback can only ever produce a loud failure rather than a window and a false success. The tests run the window binary with its environment cleared, so no display exists to open anything on. Before this, both of them reached iced.
`session` answers "is the anchor I recorded still holding this environment?" - a question about one pid - and answered it by reading `/proc/<pid>/mountinfo` for every process on the machine. That scan was measured at 37 ms on a 454-process machine, out of a 32 ms warm launch: Raven's whole cost on the path every double-clicked program takes was the scan, and `run` pays it before it can join anything. Reading the one process's `mountinfo` gives the same answer - the scan's only filter is that needle - so `holds` asks about one pid and `session` uses it. Measured on the same machine and environment, `raven run games -- /bin/true` with a session up: before 33 35 32 32 32 33 30 ms after 2 2 2 2 2 2 2 ms The check that made a recorded pid trustworthy is unchanged, and the integration test now pins both halves of it against a real mount: the cheap question must agree with the scan that found the holder, and a recorded pid that holds nothing must still be refused.
`run` joins the session's mount namespace before it execs, and `setns(CLONE_NEWNS)` resets the process's working directory to that namespace's root. Everything still relative by then was therefore resolved against `/`: `raven launch demo/game.exe` had wine look under `/demo`, and a bare `game.exe` - whose directory was deliberately left unset - under `/`. Only an absolute path worked, which is what a file manager passes and why this survived a corpus. `launch::target` settles both the program and its directory while the caller's own directory is still ours. It is lexical rather than `canonicalize`, because a program inside a live environment lives under a mount point that exists only in the anchor's namespace - resolving through the filesystem would fail for exactly the paths that do work. A working directory that is not there is now skipped instead of fatal, and tested where the answer is true - inside the namespace. Setting it anyway made `exec` fail with an ENOENT that the message rendered as "could not run wine", which sent the reader after a Wine that was installed and fine.
Two ways the same asymmetry showed. `attach` refuses a letter that already carries any mapping, on the grounds that a `"d:"="cdrom"` made in winecfg is not Raven's to overwrite - but `detach` accepted any entry at all and deleted it, so `raven env detach d` silently destroyed a mapping Raven never made. It now recognises its own entries by the form it writes, and says the letter is not attached otherwise. And the renumbering counted the wrong set. mountmgr numbers a drive by its rank in the Drives section whether or not anything is wired to it, which is what `attach` and `attachments` both count; `detach` renumbered by rank among the entries that had a device behind them. With any floppy entry that has no raw link - a half-finished attach, or one made by hand - every survivor was then wired to a `physicaldriveN` naming a different disk from the one `attachments` reports.
`base deploy` applied a Windows straight into the directory that names it, and cleaned up only when `wimlib-imagex` exited non-zero. That is the one failure it cannot count on: ten minutes over 143 886 files is long enough to be interrupted, and an interruption usually kills Raven too, so the cleanup never runs. What was left was half a Windows wearing a finished base's name - listed by `base list`, offered to `env create`, refused by a second `deploy` as already existing, and removable only by hand, since there is no command that deletes a base. It now applies into a hidden partial directory and renames only once the image is whole and its junctions repointed. A rename within one directory is atomic, so the name appears on a finished base or not at all, and a leftover partial is neither listed nor in the way: the next deploy clears it.
Installing a runtime copies libraries into the upper layer, edits the prefix's registry, and only then records what it did. Everything between the first copy and that record could fail - and did, in a full disk or a read-only prefix - leaving libraries Raven had put there and did not list. `dxvk` reported nothing installed, `--remove` removed nothing while saying the real Windows was uncovered, and the next install refused each of those files as somebody else's, telling the user to move aside what Raven had written itself. The record now goes down as soon as the files exist and describes a superset of them until the install narrows it at the end. `d3d` already skips entries that are not files, so a superset costs nothing and always leaves something that `--remove` can act on. The rollback also stopped short of the previous install. An upgrade copies over the libraries already there, and undoing it deleted them too, so a failed upgrade uninstalled the build that was working. It now removes only the files this call created. Both are pinned by tests that fail without the change: one makes the registry unwritable after the copies, the other fails an upgrade half way and requires every library of the working build to survive it.
A rules file that exists and cannot be read was treated as one that is not there, and "not there" means the defaults. The defaults are the *wider* set, so a file someone had narrowed by hand - saved by an editor in Latin-1 with one accented character, or left unreadable - was replaced by rules that project more of the base's registry than its author allowed, without a word. Only a genuinely absent file falls back now; anything else is reported with the path. And `create`'s rollback used `remove_dir_all`, which this module documents as unable to delete an environment that has ever been mounted: overlayfs leaves a `work/work` with no permissions and the removal stops there, having already deleted the upper layer. Creating an environment mounts it, so the rollback was exactly the case that cannot work; it now uses the same `remove_tree` that `destroy` does.
…ngs failed Three things the window got wrong about telling the truth. Its Diagnostics screen showed four judgements and left out the one `raven doctor` leads with: who the kernel hands a double-clicked `.exe` to. Wine registers a handler for the same magic, the kernel silently picks the most recently registered, and losing that race looks exactly like Raven losing its prefix - an hour, once. Someone using a window rather than a terminal is precisely who would never find that out, so the screen now carries it. A failed deployment showed the last line of the child's stderr, which for Raven's own failures is the errno anyhow prints under the sentence: "No such file or directory", true of a thousand failures and a description of none. The sentence wins when there is one, and wimlib's own last-line diagnostics still work as before. And the banner's "Stop the session" could outlive the judgement behind its words. It is raised when only Raven's anchor holds the environment and is deliberately exempt from the poll that clears other banners, so a game started in between would have been killed by a button that promised it would not be. It asks again before acting, and the banner relabels itself to "Stop everything using it" if the answer has changed. `raven doctor`'s media playback line also lines up with the others now.
Becoming a workspace left `examples/` at the repository root, where it is part of no package: `cargo run --example project_real_base` answered "no example target named project_real_base in default-run packages" and the one tool the project has for checking a projection against a real Windows had been unbuildable since. It moves in beside the library it exercises, and runs: 1894 keys in 82 ms against the deployed base, with all six of its refusals holding.
…ease does `pkgver()` printed a literal 0.1.0 that stayed put through two releases, so a package built from a 0.3.0 tree called itself 0.1.0 and pacman read an upgrade as a downgrade. It reads the workspace version now, from the line release-please writes and both crates inherit. CI ran `cargo test` and nothing locked, while the release workflow and the PKGBUILD both build with `--locked`. A Cargo.lock that no longer agreed with Cargo.toml therefore passed every check and failed at the release, which is the expensive and public place to discover it. CI does that build too. And `conf_path` was labelled "where the packaged registration belongs" while naming `/etc/binfmt.d`, which is where an *admin* file goes and which takes precedence over the `/usr/lib` file the package installs - the same mechanism the Wine mask relies on. The path is right for what it is used for, `raven binfmt` printing what to register when nothing has; only its name for itself was wrong, in the source and in the shipped conf file's own header.
The branch forked one commit before main's release commit, so its version files still said 0.2.0 while v0.3.0 was tagged and published. Merging it now rather than at the end settles the one conflict here, where it can be built and tested, instead of in the merge that lands the window. Cargo.toml keeps the workspace layout and takes the released version - release-please owns that line, and hand-writing it anywhere else is how the two get out of step. The manifest and the changelog were never touched by this branch and come across unchanged.
The card's Start mounted the environment and stopped there. Mounting is the cheap half - hundredths of a second - and the seconds a first launch pays are Wine's services coming up, which the CLI's `env start` deliberately waits for and the window did not. So the card flipped to "running", read as warm, and the next double-clicked program still paid the two seconds the button was pressed to avoid. The warm-up moves into the library and both callers use it. A launcher that lost the start race waited the full thirty seconds even when the winner had already given up: it watched only for a session, never for the lock going away. The lock disappearing is the holder saying it is no longer starting one - and it is the same signal that clears a lock left behind by a launcher that was killed, which cost the next launch the same thirty seconds. An anchor's failure reached the user with its reason removed: `Error::Layer` reads "preparing the layer failed at <path>" and keeps the errno as a source that anyhow prints for the CLI and this path drops. The anchor's one line is all a launcher ever shows, and "permission denied" is the difference between a bug and a chmod. And the commonest `--from` mistake said "does not look like a build", having been given an empty runtime name to print. It names the runtime it wanted.
Fifteen files carried claims that had stopped being true. The audit that found them checked each against the source; each correction below was re-checked before it was written. The shadow set says one entry, not two: `Windows/Fonts` was withdrawn on correctness grounds and `reconcile` un-masks it at every session mount, so every measurement taken under that mask - the 135 ms spawn, the 1.19x ratio, the ~20 ms launch overhead - is labelled as history rather than quoted as current. Sessions exist, so the pages that said concurrent launches into one environment are unsupported, refuse, or are an open item now say they join, and the lifecycle table describes an anchor rather than a mount that dies with the program. The repository is a workspace, so the trees and paths name crates/raven/src and crates/raven-gui/src, and the architecture page records that the first of its four planned splits has happened. Two claims were wrong in a way worth naming. The projection page said COM registrations "genuinely carry over" while `HKLM\Software\Classes` is on the deny list with a comment explaining why - the opposite of what the page said, and the corpus's own `com` category depends on the refusal. And it listed four tests, two of which do not exist; the list is now the eight that do. The `\Device\` rewrite it promised is stated as known and unhandled, because it is. The packaging page said the registration is installed to /etc/binfmt.d; it goes to /usr/lib/binfmt.d, and /etc is what a hand-registration uses to override it. It also said both binary names report as `raven` - `rvn --help` prints `Usage: rvn`, checked. The README, the status page and the corpus record what the project has gained: a second game rendering through DXVK on the GPU, vkd3d-proton installed and still unproven, twenty leaf commands counted from --help, and the window - named, with its screens listed and nothing said about how it looks, since it has never been opened. Today's measurement is in performance.md: Raven's own share of a warm launch, 32 ms down to 2.
The atomic deploy hid every directory beginning with a dot, which is more than it needed: a base a user had named with a leading dot was listed before that change and would have silently disappeared. The rule is now the exact prefix Raven writes, so nothing that was visible stops being visible.
The re-check added to the stop button refused whenever something other than Raven's own anchor held the environment - which is the ordinary state from the moment Start warms Wine, since `wineserver` and the services hold the mount too. So every Stop the user can see went dead: the card's, the detail screen's, and the banner's own "Stop everything using it", which is offered only for that case and therefore could never do the thing it named. Pressing it re-raised its own banner, and the environment could only be released from a terminal. The judgement and the button are separated now. `Action::message` says which stop each banner asks for, so the mapping is one function with a test on it, and only the stop offered as "the session" - whose label promises nothing of the user's is running, on a banner that outlives the poll - looks again before acting. Two fixes claimed by the previous commit were not in it: a Python script aborted on its first substitution and the rest never ran. They are here. A launcher waiting on the start lock now stops the moment the lock goes away rather than sitting out the full thirty seconds after the holder has given up, and an anchor's failure carries its cause, so "preparing the layer failed at <path>" arrives with the errno that says whether it is a bug or a chmod. Also from the review: an interrupted Direct3D upgrade no longer reports itself as the build it half-replaced; `raven doctor` names an interrupted deploy, which is several gigabytes under a name `base list` hides on purpose, and `Base::find` refuses that name; `unpack`'s doc comment is back on `unpack`; CI asserts the lock through `cargo test --locked` rather than a second fat-LTO release build; and two comments and two documentation sentences that were wrong about their own subject are right.
`launch::target` asked for the caller's directory before deciding anything, so a launch by absolute path - which is what a file manager passes - failed outright when that directory had been deleted underneath the shell. An absolute path is already what it means, so it is resolved without asking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Raven becomes a workspace of two crates: the library with its command line,
unchanged, and
raven-gui— a window over the same library. The CLI is stillthe primary interface and nothing here requires the window.
The window
Four screens, built on
colony-uiover iced 0.14: environments with Start andStop on the card, a per-environment detail (session, Direct3D, devices,
registry), bases with a real deployment progress bar read from
wimlib's ownoutput, and diagnostics. Errors with an obvious action become that action —
raven env stop gamesbecomes a button rather than a command to retype.Deliberately absent, each deferred to its own spec: a file dialog, a settings
screen, the block-device picker, and the launch/loading window.
Why now: the corpus only grows through users, and users who will not type
commands never arrive. Seven programs tested is too few to know anything, and
no feature widens that number — an interface does.
The audit
The branch was then audited whole: twelve lenses over the repository, every
finding put to two independent refuters (one on the fact, one on whether it was
worth acting on), and a completeness critic. 93 findings, 66 actionable, each
verified again by hand before anything changed. Three were refuted by
experiment rather than argument — CR/LF injection into a projected
.reg(tested against the real Windows base: 1 894 keys, zero malformed lines) and
tarpath traversal from a user-supplied DXVK archive (GNU tar refuses boththe
..and the symlink variant, and Raven checks the exit status).A warm launch costs 2 ms of Raven's own overhead, down from 32. Validating
the recorded session anchor asked whether one process still held the
environment, and answered it by reading
/proc/<pid>/mountinfofor everyprocess on the machine. Reading the one process's file gives the same answer:
measured seven times before and after,
raven run <env> -- /bin/truewith asession up went 33 35 32 32 32 33 30 ms → 2 2 2 2 2 2 2 ms.
Fixed along the way, each with a test that fails without it:
and mounting for a registry import — and only the first had been taught to
the window. Reproject therefore launched
raven-gui exec …, which opened asecond window and then reported success having projected nothing.
every form:
setnsresets the working directory to the namespace root, soanything still relative by then resolved against
/.detachdeleted drive-letter mappings made by hand in winecfg, whichattachgoes out of its way not to touch, and renumberedphysicaldriveNby a rank mountmgr does not use.
base deployleft half a Windows wearing a finished base'sname — listed, offered to
env create, refused by the next deploy andremovable only by hand. It now applies into a hidden directory and renames.
neither see nor remove, and a failed upgrade deleted the working build it was
replacing.
wider default rules;
env create's rollback used the removal that cannotdelete a mounted environment.
while the first launch still paid the two seconds the button exists to avoid.
Documentation
Fifteen files carried claims that had stopped being true — the shadow set's
size, sessions that the pages still called unbuilt, root-level
src/pathsafter the workspace split. Two were wrong in a way worth naming: the projection
page said COM registrations carry over while
HKLM\Software\Classessits onthe deny list with a comment explaining why, and it listed four tests of which
two do not exist.
Before merging
The window has never been opened. It compiles, its tests pass, and it has
been read closely — but nobody has looked at it. That check is five minutes in
front of the screen and it is the one thing between this and a release.