Repository navigation
nvx-rs: make the host module's dependency graph acyclic - #910
Merged
Pedro Henrique Penna (ppenna) merged 4 commits intoOct 27, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The dependency guard can silently miss cycles through chained or conditional re-exports.
2 open issues
What changed in this PR
This PR removes dependency cycles between nvx::host’s direct children, establishing the layering needed for the module refactoring in #896 while preserving existing public paths.
Changes:
- Moves inherited handles, VMM spawning, and artifact checks into lower-level modules.
- Adds a standard-library-only dependency guard with parser and cycle-detection tests.
- Documents the layering rule and its existing CI integration.
| File | Description |
|---|---|
nvx-rs/tests/host_modules.rs |
Adds dependency analysis and cycle-detection tests. |
nvx-rs/src/host/supervisor/mod.rs |
Preserves public paths through re-exports. |
nvx-rs/src/host/store/mod.rs |
Imports Launched directly from spawn. |
nvx-rs/src/host/spawn.rs |
Relocates VMM spawning and corrects its documentation link. |
nvx-rs/src/host/sandbox/plan.rs |
Uses artifact-module file validation. |
nvx-rs/src/host/sandbox/mod.rs |
Delegates file checks while retaining require_file. |
nvx-rs/src/host/platform/windows.rs |
Imports inherited handles from the platform layer. |
nvx-rs/src/host/platform/other.rs |
Uses the relocated inherited-handle type. |
nvx-rs/src/host/platform/mod.rs |
Declares the crate-visible inherit module. |
nvx-rs/src/host/platform/linux.rs |
Imports inherited descriptors from the platform layer. |
nvx-rs/src/host/platform/inherit.rs |
Holds inherited descriptor and handle abstractions. |
nvx-rs/src/host/mod.rs |
Declares private spawning and documents layering. |
nvx-rs/src/host/cli/sandbox/mod.rs |
Imports artifact checks from their new location. |
nvx-rs/src/host/cli/run.rs |
Uses the relocated artifact check. |
nvx-rs/src/host/artifacts.rs |
Owns the shared artifact and file checks. |
nvx-rs/README.md |
Describes the dependency guard. |
doc/ci.md |
Documents the guard’s inclusion in CI. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 20:29
efc0292 to
5a8fe93
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 20:44
5a8fe93 to
1992556
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 20:55
1992556 to
05c10ac
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 21:18
05c10ac to
682046d
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 21:36
682046d to
a1682c0
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 21:53
a1682c0 to
9234af4
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/issue-897-acyclic-host-graph
branch
from
October 26, 2026 22:08
9234af4 to
87dc0a7
Compare
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; an inline module in the input of a macro, which may put the module's code in another module; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that names or may join a path from a module or from the macro's input, or that holds one in a string, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that may make a keyword that declares of an identifier of its input, as $kind $name { ... } may be mod name { ... }, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that names or may join a path from a module or from the macro's input, or that holds one in a string, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, as $vis $kind $name { ... } may be mod name { ... }, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that names or may join a path from a module or from the macro's input, or that holds one in a string, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; in the input of a macro that declares a module in a file of its own, a path = or a macro_use, of which a template may make an attribute of the module; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that names or may join a path from a module or from the macro's input, or that holds one in a string, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; in the input of a macro that declares a module in a file of its own, a path = or a macro_use, of which a template may make an attribute of the module, and in that of one that defines a macro, a macro_export outside its own attributes; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file, brings macros into scope, or exports a macro, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; in the input of a macro that declares a module in a file of its own, a path or a macro_use, of which a template may make an attribute of the module, and in that of one that defines a macro, a macro_export outside its own attributes; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file, brings macros into scope, or exports a macro, or one that its input completes before a macro_rules!, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, and before a group of an import, as $kind { self as root } may be, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules and imports by name, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; in the input of a macro that declares a module in a file of its own, a path or a macro_use, of which a template may make an attribute of the module, and in that of one that defines a macro, a macro_export outside its own attributes; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file, brings macros into scope, or exports a macro, or one that its input completes before a macro_rules!, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, and before a group that an import may hold, any but an empty one or one with a colon or a dot at its top level, which no import holds, as $kind { $path } may be use { crate::host }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; in the input of a macro that declares a module in a file of its own, a path or a macro_use, of which a template may make an attribute of the module; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, and before a group that an import may hold, any but an empty one or one that holds at its top level a token that no import holds, such as a colon, a dot, or a semicolon, as $kind { $path } may be use { crate::host }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, as $vis $kind $name { ... } may be mod name { ... }, and before a group that an import may hold, any but an empty one or one that holds at its top level a token that no import holds, such as a colon, a dot, a semicolon, or a keyword but as, as $kind { $path } may be use { crate::host }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that an import may hold, any but an empty one or one that holds at its top level a token that no import holds, such as a colon, a dot, a semicolon, or a keyword but as, as $kind { $path } may be use { crate::host }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that may expand to the group of an import, as $kind { $path } may to use { crate::host }, unlike $name { field: value } or $name { $($field: $value),* }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, past any :: before it in the input of a macro that it invokes, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, even past a visibility of its input, which may be empty, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that may expand to the group of an import, as $kind { $path } may to use { crate::host }, unlike $name { field: value } or $name { $($field: $value),* }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, past any :: before it in the input of a macro that it invokes, or that holds one in a string, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, even past a visibility of its input, which may be empty, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, or a block of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that may expand to the group of an import, as $kind { $path } may to use { crate::host }, unlike $name { field: value } or $name { $($field: $value),* }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, or a doc comment in a macro's input, of which Rust makes such a string, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; an identifier that is not ASCII, which Rust takes for any spelling that Unicode's NFC normalization makes alike; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, past any :: before it in the input of a macro that it invokes, or that holds one in a string, or in a doc comment in the input of a macro that it invokes, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, even past a visibility of its input, which may be empty, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, a block, or an expression of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that may expand to the group of an import, as $kind { $path } may to use { crate::host }, unlike $name { field: value } or $name { $($field: $value),* }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
The fix of #897 in #910 moved require_artifact and require_existing from host::sandbox into host::artifacts, and the spawn of a VMM from host::supervisor into the private host::spawn. Their documentation still described them from where they had been: require_artifact and require_existing described themselves by sandbox::require_file, which itself calls require_existing, and the module documentation of host described spawn only in its paragraph on the layering of host. The independent fix of #897 in #909, which #910 superseded, worded both more precisely. Take its wording. require_artifact now refers to require_existing, beside it. require_existing states what it checks and how it fails: that the path is a regular file, following links, as require_file of nvx.py checks it, or else the error code that its caller passes and the message `{description} not found: {path}`, with the path spelled with its components alone, as Path::components reads them. The supervisor's entry in the module documentation of host now describes spawn: a private module, whose items supervisor re-exports, that starts the VMM detached, with no other descriptor or handle of the caller's. The paragraph on the layering of host no longer repeats this, and says only that the store builds on spawn. Only documentation changes: the public API, the messages, the error codes, and the tests stay the same. Validation at this commit, on dev: rustdoc with warnings denied; Clippy with warnings denied for all targets with all features, for Windows and Linux; cargo check for macOS; and the tests with all features on Windows (822), and the library's tests with all features on Linux (1237), pass. Part of #897. Part of #896. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 27, 2026
tests/host_modules.rs, which #910 added, keeps the direct children of nvx::host free of cycles. #909, an independent fix of #897 that #910 superseded, had a test that follows more of the code through which a child may name another, and that reports a cycle as #897 asks, with one reference for each of its dependencies. Fold it in, so that the test does what both did. The test now reads the crate's sources from src/lib.rs through every module that the crate declares, in a file of its own or inline, where it read src/lib.rs and the files beneath src/host/ alone, so it sees every module through whose imports a child may name another. A file beneath src/host/ that no mod declares still fails it. It still reads text, with std alone, and blanks comments and literals. It leaves out test-only code: what a cfg that requires test guards, directly, through a cfg_attr that applies attributes, not one in what another attribute takes as its arguments, or as the inner attribute of a module or a file, with the files of the modules that such code declares, and #[test] functions. It no longer takes an inline module for test-only code by its name, tests, alone. It leaves out guarded code only as far as its tokens show where that code ends, as before, and now also leaves out an element of a list, such as a field of a struct expression or a parameter of generics or of a closure, up to its first comma, >, |, or the end of the list, which it counted whole. The input of a macro, which need not be Rust, and whose attributes the macro may keep or drop, is production code throughout: no cfg in it leaves anything out, every path in its tokens counts, from any of them, past a :: before it, however the test reads the rest of it, as in a use tree that the macro takes apart, and so does every path in its strings, even one after doc =, which the macro need not take for documentation; and the test finds there what it refuses wherever it is, as an include! in a use tree, and every module that the input declares in a file of its own. So the test may find a dependency in test-only code, but never misses one in production code. A child depends on another when its production code names a path into the other: a crate::, super::, self::, or self::super:: path, or, now, one from a name that extern crate self as NAME, at the top level of the crate root's file, gives the crate root in the extern prelude, unless the module, defined once, declares a module or crate of that name, or imports one of the crate's modules under it, through modules, imports by name, and names that such a declaration without a cfg gives, in every configuration. Such a declaration in the crate root, which the test refused, is now followed; a child's import of the crate root or host itself still fails it. The path may be in a use tree, an expression, an attribute, or, with its escapes decoded, a string of an attribute or of a macro's input, or a doc comment in a macro's input, of which Rust makes such a string, but not in documentation that an attribute applies outside the input of a macro. It may go through the names that any module outside the children, not only the crate root and host, binds with its imports, by name, under an alias, or with a glob, which the test reads to a fixed point under every configuration, through a module's import of itself, under any alias, as often as a path goes through it, and a child's import of such a module depends on every child that the module's names stand for. A macro that a child exports is still an item of the crate root, in the namespace of macros. Code whose dependencies the test cannot follow fails it: a #[path] attribute anywhere in the crate, where only host's failed it; an identifier that is not ASCII, in code or in a path of a string or a doc comment, which Rust takes for any spelling that Unicode's NFC normalization makes alike; include!, under any name that imports may give it, unless a macro_rules! of that name hides it, in the file or in a block, and through a path, which may name it whatever the crate defines; an import of host or the crate root itself in a child; a glob import of a child into a module outside them; a path through imports that alias one another in a cycle that lengthens it, which imports of different configurations may make; an inline module in the input of a macro, which may put the module's code in another module, or an alias of the crate root in the input of one in the crate root, which may join the extern prelude; a module that the input of a macro declares in a file of its own, where the input of a macro or a template holds a path or a macro_use anywhere, of which a template, maybe through other macros, may make an attribute that sets the module's file or brings its macros into scope; a macro_export anywhere in the input of a macro or in a template, of which a template may make an attribute that exports a macro at the crate root, from where the test cannot tell; a #[macro_use] attribute in host; and the template of a macro_rules! that declares or imports, that holds an attribute that sets a module's file or brings macros into scope, that aliases the crate root or a module above it, as $kind {crate as this} may, that names or may join a path from a module or from the macro's input, past any :: before it in the input of a macro that it invokes, or that holds one in a string, or in a doc comment in the input of a macro that it invokes, but documentation outside the input of a macro that it invokes, which resolve where the macro expands, that puts another file's code in with include!, that invokes a macro that its input names, which may be include!, that puts extern before an identifier or a token tree of its input, which may complete extern crate, even past a visibility of its input, which may be empty, that may make a keyword that declares of an identifier of its input, after a visibility of its input or none, or an item, a statement, a block, or an expression of its input, or at the end of a repetition, or anywhere in the input of a macro that it invokes, which may take any part of it for an item, as $vis $kind $name { ... } may be mod name { ... }, and before a group that may expand to the group of an import, as $kind { $path } may to use { crate::host }, unlike $name { field: value } or $name { $($field: $value),* }, or before the token that follows it when a repetition that it ends repeats, as $($kind)* may be use nvx, or that invokes a macro by name under local_inner_macros. A cycle fails the test, which now lists, for each dependency on one, the shortest cycle through it, with the first reference along each of its dependencies, under how to break it, where it listed each strongly connected component. Without a cycle, the children stack in layers, each above those that it depends on, which the test prints with --nocapture. On the dev that #910 fixed, at 12b5613, the test lists 13 cycles, each through one of the three dependencies that #910 removed. On this tree, it finds the same 51 dependencies as #909's test, in the same ten layers as before. The ten tests of #909 run beside the test's twelve, with their fixtures unchanged, in place of the test's previous thirteen: its two tests of the report, of strongly connected components and of one reference for each dependency, give way to one of the shortest cycle through each dependency, and its test of guarded code whose end the tokens do not show is renamed for what it now checks, that such code is test-only no further than its tokens show. Each of the 22 findings of #909's reviews reproduces as a fixture against the test, and passes; eight failed on the test before, and #909's tests pin them. A separate review of the fold, in five rounds, found nineteen more ways to misread code, and its fixes two more, each with a regression fixture that fails on the test before its fix. Two stay on purpose: a path-qualified include! fails the test, whatever it names, and a name that a glob brings in hides no name of the extern prelude, since what a glob brings in depends on visibility, which the test does not read. The module documentation of host and the README's testing section describe the test as it now is. Validation, on dev. The test's 22 tests pass. On Windows, the check-nvx-rs steps that run there pass: rustfmt; Clippy with warnings denied for all targets with all features, for the default-feature library, and for the library without default features; the tests with all features (831), with default features (686), and of the library without default features (201); the artifact-bundling test (18); rustdoc with warnings denied; the Rust 1.89 check; and the runtime adapter's protocol-only (39 tests) and host-only (84 tests) configurations. So do the check-aci-edge-sandboxes steps that run on Windows, with 168 and 89 tests and agent bundling, and the Markdown link check. On Linux, under WSL 2, the Linux steps of both actions pass too: the nvx tests with all features (1405), with default features (670), and of the library without default features (201), artifact bundling (19), the macOS check, the guest binaries' lint, tests (829), and build, the runtime adapter's guest-only configuration (106 tests) besides the others, and aci_edge_sandboxes's tests and its macOS check. Against dev, the lists of tests change only in this test's. Part of #897. Part of #896. Supersedes #909. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.


Closes #897. Part of #896. Supersedes #909.
#909 and the follow-up: #909 fixed #897 independently in another session and was closed as a duplicate of this PR. This PR merged at
71a68acf, before its fold-in of #909's improvements was pushed. The fold-in follows in #919, which lists what it takes from #909:spawn, the layering ofhost, and the README's testing paragraph.The red-on-
devoutput below is that of the guard that merged here; #919 shows the new report.The direct children of
nvx::hostnow depend on one another without cycles, and a std-only integration test,nvx-rs/tests/host_modules.rs, keeps them so. Three items move down, each re-exported at its old public path, so neither behavior nor the public API changes.Commits
Inherited, with itsBorrowedalias and impls, moves fromsupervisor/child.rsinto a new crate-visiblehost::platform::inherit, which the Linux, Windows, and other-host platform modules import.supervisorre-exports it, sonvx::host::supervisor::Inheritedstays its only public path. This removesplatform→supervisor.supervisor/child.rsmoves, withgit mv, to the privatehost::spawn.supervisorre-exportsLaunched,VmmCommand, andspawn, andstoreimportssuper::spawn::Launched. The doc link tosupervisor::launchnow uses acrate::path, sincesuper::launchwould silently name thehost::launchmodule from the new file (nvx-rs: break the host module into finer-grained modules #896, invariant 6). This removesstore→supervisor.require_artifactandrequire_existingmove fromsandboxintoartifactsaspub(crate).require_existingstill spells a missing path withhost::paths::normalized, soartifactsnow imports the std-onlypathsmodule, which sits beneath both, and the sandbox tests importpathsthemselves. The publicsandbox::require_filestays and callsartifacts::require_existing, andcli/run.rsandcli/sandbox/mod.rsimportrequire_artifactfromartifacts. This removesartifacts→sandbox.host's module docs, and describes the test in the nvx-rs README anddoc/ci.md.The guard
tests/host_modules.rsreadssrc/lib.rsand the files undersrc/host/as text, with std alone. It needs no new dependency, and it runs in check-nvx-rs's existingcargo teststeps without a workflow change. It:moddeclarations and inline modules, and leaves out test-only code: code under acfgthat requirestest(cfg(test)orcfg(all(test, unix)), but notcfg(not(test))orcfg(any(test, feature = "cli"))), the files of the modules that such code declares,#[test]functions, and inlinetestsmodules. It excludes guarded code only as far as bracket structure shows where that code ends: an item, with angle brackets tracked in its header; a statement ending in;; a macro invocation; a block,if(with itselsebranches),match, or loop; or a field, parameter, variant, or arm of the list that declares it, where an arm whose expression has a block needs no comma. In ordinary code, only the start of the file or a{,}, or;before the attributes shows that an item or statement starts. A scan for such an end stops at the start of another item, for example after an item kind the guard doesn't know. Other guarded code, such as a field of a struct expression, or a generic or closure parameter, counts. The guard may therefore report a dependency from test-only code, but it never misses one from production code;crate::,super::, andself::path, inusetrees and elsewhere, including attributes, such as a derive, and their strings, such as serde'swith = "...", to the child ofhostthat holds what it names, through the module-level imports of the crate root andhost, such ascrate::Artifacts. It reads those imports to a fixed point, since one may name what another imports, in either file and order, or rename what its own module imports by the name alone, and a name thatcfgs import from several children, or that a module also has under othercfgs, depends on each of them. A macro that a child exports with#[macro_export], directly or throughcfg_attr, and from its file or an inline module, is a crate-root item held by that child. An outer attribute's paths are test-only where what it applies to is, and an inner attribute's where its region is. A raw identifier, such asr#type, names whattypewould.cfg(unix)andcfg(windows)code both count.A cycle fails the test with each strongly connected component and one reference for each dependency in it. The test also fails on code whose dependencies it cannot follow: a
#[path]attribute, a file that nomoddeclares, an import ofhostor the crate root itself, includingextern crate self as this;, a#[macro_use]attribute inhost(directly or throughcfg_attr), and a glob import from a child into either. This also covers#[path]and#[macro_use]in ahostthat the crate root declares inline.Red on
dev, with only the test file added:the_graph_holds_well_known_dependencies_of_the_sourcesfails there too, sincedevhas nohost::spawn. After commit 1, the cycle shrinks toartifacts, launch, sandbox, store, supervisor; after commit 2, toartifacts, launch, sandbox, supervisor; after commit 3, it is gone, and all thirteen tests pass.Resulting layers, lowest first, as the issue predicted:
network,paths,report,seal,sysplatformfilesystem,nvxs,processsession,spawnstoreartifactslaunchsupervisorinstance,sandboxcliCoordination with the template stack
host::template, from the template and clone stack (#879 to #888), is not ondevyet. At the head of #888,store→template(template::Lease) andsandbox→template(template::BlockGeometry) would close a cycle again. If this PR lands first, the guard flags both edges, with a reference for each, when the stack rebases, and the stack resolves them as #897 describes.Validation
I ran the checks locally on Windows 11 and on Linux under WSL 2 (Ubuntu 26.04, kernel 6.18), with Rust 1.95.0 and the 1.89 MSRV, on
devat12b56130. No check needs a hypervisor.Rebase:
devmoved fromff3cedf6to12b56130, whose #911 deleteshost::pythonand hassandbox::require_existingspell paths with a new std-onlyhost::pathsmodule. Commits 1 and 2 applied cleanly, and commit 4 needed only the README's list of tests merged. I redid commit 3 on the new code:require_pure_fileis gone, so onlyrequire_artifactandrequire_existingmove. On the newdev, the guard finds the same eight-child cycle with the same three back-edges, and the branch has the same layers, withpathsin place ofpython. The graph now has 51 dependencies, since #911 also adds two downward ones,sandbox→platformandstore→seal.RUSTC_BOOTSTRAP=1 cargo rustdoc --lib --all-features -- -Z unstable-options --output-format json) forx86_64-pc-windows-msvcandx86_64-unknown-linux-gnu. I walked it from the crate root through public paths, re-exports, fields, variants, inherent methods, and trait impls, including synthetic and blanket ones. Each of the four commits has 0 differences fromdev, over 9,632 entries on Windows and 13,779 on Linux. The moved items appear only atnvx::host::supervisor::{Inherited, Launched, VmmCommand, spawn}andnvx::host::sandbox::require_file; noplatform::inheritorhost::spawnpath is public.cargo test -- --listwithdev, with all features and with default ones. On Windows, the only change is the thirteen newhost_modulestests. On Linux, the moved module's four Linux-only tests are also renamed fromhost::supervisor::child::tests::*tohost::spawn::tests::*. Theartifactsbundling filter selects the same tests as before.-D warningsfor all targets with all features, the lib, and the lib without default features; tests with all features (822 passed), default features (677), and the lib without default features (201); artifact bundling (18); rustdoc with-D warnings; the 1.89 check; and the runtime adapter's protocol-only (36) and host-only (76) configurations with their MSRV checks.-D warningsfor Windows and Linux,cargo checkfor macOS, and the Windows tests with all features (809) pass. The Linux library tests (1,237) pass at commits 1 and 2.Test counts are each test binary's own
test resulttotals. Some tests run their own binary again as a child, whose nested totals are not counted.The hypervisor-backed tests are left to CI.
Review
Copilot's ten reviews found twenty-five ways for the guard to misread a reference. The guard commit now handles each one, with regression fixtures. Each fixture fails on the guard before the fix and passes now. No fix changes the graph of the real sources: 49 dependencies on the old base, and 51, in the same layers, on the new one.
First review, covered by
imports_of_the_root_and_host_resolve_through_chains_and_every_alternative:host/mod.rssorts beforelib.rs, so a single pass droppedhost'spub use crate::Item;of what the root imports from a child. The imports are now read to a fixed point.#[cfg(unix)] pub use host::a::Thing;and#[cfg(windows)] pub use host::b::Thing;overwrote each other. Each imported name now keeps every child that it may come from, and a path through it depends on each one.Second review:
pub use Item as Alias;afterpub use host::a::Item;was skipped, sinceItemis an imported name rather than a module. Imports may now start with a name that their module imports. The fixture, added to the test above, gave[]before the fix.super::r#b::Xwas split atr#and resolved outsidehost, andmod r#b;declared nothing. Raw identifiers are now single tokens that name what they would withoutr#. The newraw_identifiers_name_what_their_names_dotest checks the cycle that such a path closes.#[cfg(test)] value: Result<super::b::X, super::b::Y>,ended at the comma inside<...>, soYcounted as production. A test-only named field or parameter now ends at the first comma outside its type's angle brackets, and an arrow's>, as infn(u8) -> T, closes none. Cases intest_only_code_is_not_a_dependencycover fields and parameters. Before the fix, they gave a spuriousa -> b.Third review:
#[cfg(unix)] pub mod backend { ... }and#[cfg(windows)] pub use host::b as backend;inlib.rs, the module hid the import, socrate::backend::run()losta -> b. The imports of the crate root andhostalong a path now count beside any module of the same name. A fixture in the import test covers that cycle. Before the fix, it gave onlyb -> a.elsebranches of a test-onlyif: the guard of#[cfg(test)] if ... { ... } else { ... }ended with the first block, so theelsecode counted as production. A}followed byelsenow keeps the guard for the next block.test_only_code_is_not_a_dependencycovers anif,else if, andelsechain, followed by a production call that still counts. Before the fix, it gave a spuriousa -> b.Fourth review: three findings showed the same weakness in the earlier field and
elsefixes. When the guard guessed where test-only code ends, a wrong guess could extend the exclusion over production code and hide a dependency. The guard now uses the extent rule above instead, so an exclusion can end too early but never too late:Data { #[cfg(test)] debug: 1 < 2, value: super::b::helper() }hidvalue, because the earlier field fix read<as a generic bracket. A guarded field of a struct expression now counts.#[cfg(test)] crate::make_tests! {}left the guard open over the next function. Macro invocations through a path now end after their arguments.#[cfg(test)] fn helper<T: Bound<{1}>>() { ... }ended at{1}. Items now end at the body after their generics. The other example,#[cfg(test)] if { true } { ... }, still ends at its block condition, so itsifblock counts. A new test,guarded_code_whose_end_the_tokens_do_not_show_counts, covers it and the struct-expression field. Taking the second block as the body would hide production code afterif c { a } { b }.pub struct S(#[cfg(test)] Result<X, Y>);ended at the generic comma. Fields and parameters of declared structs, unions, variants, and functions now end at a comma outside their type's angle brackets.fn helper() { use crate::host::b::Item; }inlib.rsorhostwas treated as a re-export. Only module-level imports now name items for the crate root andhost.Each of the five cases failed on the guard before this round and passes now. All earlier fixtures still pass, and the real graph is unchanged. Fifth review: two more ways for an exclusion to end too late.
#[cfg(test)] 0 => loop { break }needs no comma, so the guard scanned to the comma of the next, production arm. An arm whose body is a block expression (a block,if,match, loop, orunsafeorconstblock) now ends after that block and anyelsechain.test_only_code_is_not_a_dependencycovers a guardedlooparm before a production arm, which still counts.enum E { A = Data { ... }.value, B = pick(...) }, the brackets afterDataandpickwere read as variant field lists. They now count as variant fields only when they follow a variant name that starts a variant, after{,,, or an attribute.guarded_code_whose_end_the_tokens_do_not_show_countscovers both discriminants.For defense in depth, scans for the end of an item header or an expression statement now stop at tokens that can only start another item or attribute, such as
pub,struct,use,let, or#. Lifetimes and labels, such as'static, are now single tokens, so-> &'static strdoesn't stop a header scan. A guardedauto trait Marker {}, a kind the guard doesn't know, can no longer extend over the next function. A fixture covers it, along with a test-only function returning&'static str.Sixth review:
async move || ...was taken for anasync move { ... }block, so a guarded arm with such a closure ran to the next arm's block. A block now needs a{afterasync move.fn f<#[cfg(test)] 'a, T>() { ... }, the lifetime was taken for a label, and#[cfg(test)] const N: usizescanned to a later;. Guarded code that follows a<or a,in ordinary code is a list element whose end the guard can't see, so it counts.guarded_code_whose_end_the_tokens_do_not_show_countscovers both, and the body and the followingusestill count.extern crate self as this;: this crate-root alias was ignored, socrate::this::host::b::...resolved outsidehost. It now fails the guard as an import of the crate root itself.I also closed the remaining way for children to depend on one another without a path: macros. A
#[macro_export]macro of a child, which is at the crate root, now resolves to that child, and#[macro_use]inhostfails the guard. There are fixtures for both.hostuses neither today. Expression-statement scans also stop atunionandmacro, which start items.Seventh review:
cfg_attr:#[cfg_attr(unix, macro_export)]and#[cfg_attr(unix, macro_use)]were ignored. Attribute checks now look insidecfg_attrunder any predicate, as the#[path]check already did. The first form registers the export, and the second fails the guard.mod a { ... }insidehost/mod.rswas attributed tohost, not toa. Each export now records the module that encloses it. I applied the same rule to#[macro_use]and#[path], which an inlinehostinlib.rscould otherwise bypass. Fixtures cover the inline cycle and both inline-hostfailures.Eighth review:
#[derive(crate::host::b::Serialize)]ina, whilebusesa's type, hida -> b. Thecrate::,super::, andself::paths in an attribute's tokens now count, and so do those in its strings, such as serde'swith = "super::b::codec", which a derive turns into code. Each counts as what the attribute applies to does: an outer attribute's paths are test-only only where its item, field, or other code is, and an inner attribute's only where its region is. A new test,paths_in_attributes_count_where_what_the_attributes_apply_to_does, covers the derive cycle,cfg_attr, string paths with their lines, and test-only items, fields, and modules.|#[cfg(test)] _unused: ()| { super::b::helper(); };, the guard ran through the closure body to the statement's;. Rather than adding|to the<and,that left guarded code counted, I inverted the rule: in ordinary code, a guard now leaves code out only after the start of the file or a{,}, or;, where an item or statement starts. This also coverscfg_if!'sif #[cfg(test)] { ... } else { ... }, whose productionelsebranch the guard had left out.guarded_code_whose_end_the_tokens_do_not_show_countscovers both.Four of the five cases that I compared failed on the previous guard: the derive cycle, a serde string path, the closure, and
cfg_if!. The fifth, a derive on a#[cfg(test)]item, passes on both, and checks that the new code doesn't count test-only attributes.Ninth review:
#[doc = "[spawn](crate::host::supervisor::spawn)"]would addplatform→supervisor, though the same link in a///comment, which is blanked, adds nothing. The strings of adoc = ...value or adoc(...)list, also withincfg_attr, now name no dependency, while helper attributes' strings, such as serde's, still count."crate::host::\x62::codec"was read ascrate::host. Strings that are not raw are now decoded first, with\x,\u{...}, the simple escapes, and line continuations, and each decoded byte keeps the line of its source.Tenth review:
#[r#path = "c.rs"],#[r#macro_use], and#[r#macro_export], also withincfg_attr, bypassed the attribute checks. They now compare names withoutr#, as do thetest,cfg, anddocchecks.host, exported bya, shared the crate root's import map with the modulehost, socrate::host::b::h()inbgainedb→a. Exports now register asNAME!, a path names a macro only with the!and delimiter of an invocation, and an import takes its name in both namespaces.Each case failed on the guard before its round: four of the five that I compared in the ninth, and five of the six in the tenth. The others pass on both, and check that serde paths beside a doc comment, and macro paths through
hostbeside anX != 1comparison, still resolve.The counts above are from the rebased branch with every fix. CI was fully green on
a1682c05; later heads were superseded before their runs finished.