Skip to content

doc: publish the nvx crate's module diagrams and keep them current - #930

Merged
Pedro Henrique Penna (ppenna) merged 6 commits into
devfrom
agents/rebase-module-diagrams-for-docs
Oct 27, 2026
Merged

Pedro Henrique Penna (ppenna) merged 6 commits into
devfrom
agents/rebase-module-diagrams-for-docs

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Publishes diagrams of the nvx crate's modules in the documentation, and has CI draw them again after every merge to dev that changes the crate. It supersedes the wip/module-diagrams branch, rebased on the current dev: its PowerShell driver and interactive HTML atlas, which GitHub shows only as source, become a cross-platform Python generator whose Markdown page of SVG images GitHub renders.

What changed

The first three commits each pass the checks below on their own, and the last three address the review:

  1. scripts: draw the module diagrams of the nvx crate: scripts/module_diagrams.py exports the modules of the crate's library, with its cli, testing, and guest features, with cargo-modules for x86_64-unknown-linux-musl and x86_64-pc-windows-msvc, and merges the two exports. It draws, with Graphviz, the top-level modules, the dependencies between them, and the hierarchy of each top-level module, and marks the modules that only one platform builds. It writes the SVG diagrams and their page to build/modules unless --output names another directory, and refuses exports that hold anything but modules and their uses. validate-nvx runs its unit tests.
  2. doc: publish the module diagrams of the nvx crate: doc/modules publishes the diagrams, drawn on Ubuntu 24.04 with cargo-modules 0.27.0 and Graphviz 2.43.0 with Liberation Sans. The README, the crate's README, and the project structure link to them, and a test checks that the directory holds exactly the page and the diagrams that it shows.
  3. ci: draw the module diagrams again after every merge to dev: module-diagrams.yml runs for pushes to dev, ready pull requests, and dispatches that change the crate, the Rust pin, the script, the build constants, or the workflow. Its credential-free draw job draws the newest commit and uploads the diagrams. For dev, its publish job commits them when they change, in a commit whose message skips CI, as the performance baselines' commits do. The new Module diagrams section of doc/ci.md documents it.
  4. scripts: restore the modules that cargo-modules mistakes for tests: unless it analyzes with cfg(test), cargo-modules (0.27.0, 0.28.0, and main) drops as a test module every module whose cfg folds true with test true and every other condition false, such as #[cfg(not(windows))] mod imp in host::sandbox::scratch, which the host diagram therefore labeled Windows only. The script now also reads the analysis with cfg(test) and each module's cfg conditions, restores the modules that build outside tests, and fails on a cfg that depends on test otherwise.
  5. ci: publish no drawing of inputs that dev has since changed: a rerun of the publish job alone reuses its run's drawing, so the job publishes it only while dev holds the inputs that it was drawn from, and leaves newer inputs to the run that their push started.
  6. scripts: count each use of a restored module once: cargo-modules' filter moves the uses of a module that it drops to the nearest module that contains it and that it keeps, so adding a restored module's uses to the plain ones counted a use that only the restored module makes twice. The script now projects each use of the analysis with cfg(test) onto the modules that the plain analysis keeps, keeps the uses that land on a plain use or on a module itself, and drops the moved plain uses that the analysis with cfg(test) does not hold. The published diagrams do not change.

Notes for reviewers

  • The publish job pushes with PERFORMANCE_PERSIST_DEPLOY_KEY, the only deploy key that may push to dev past its ruleset. It builds and runs nothing of the commit, refuses an artifact that holds anything but the page and SVG files, and commits nothing outside doc/modules. A dedicated key would only change the secret's name.
  • The layouts depend on the Graphviz release and the fonts, so the draw job runs on ubuntu-24.04 and installs Liberation Sans, as the environment that drew the committed diagrams did.
  • cargo-modules is pinned to 0.27.0, which builds with the Rust release that rust-toolchain.toml pins; 0.28.0 needs Rust 1.98.
  • The workflow is not part of Required status check. A ready pull request that changes its inputs runs the draw job alone, so a change that breaks the drawing fails before it merges.
  • The dependency diagram shows activation using host: cargo-modules counts the trait implementations of a type toward the type's module, and host::template::key (nvx-rs: derive the compatibility keys of templates #879) implements From<KeyIdentity> for activation::Identity. activation imports nothing from host.
  • dev has since gained the template store (nvx-rs: add the template store #881), which adds modules to host. The draw job of this pull request, which draws its merge, reports that it changes README.md, overview.svg, dependencies.svg, and nvx-host.svg in doc/modules, and the publish job commits them after the merge, as it will for every later change of the crate.

Validation

  • Each commit, in a worktree of its own, on Windows: ruff check and ruff format --check of scripts and .github/specula, Pyright strict for the Linux and Windows platforms, check_markdown_links.py, the module's tests, and the tests that require validate-nvx to run every test module, the build constants to own the target triples, and no file to repeat a tool pin.
  • The head, on Linux, in an Ubuntu 24.04 container with Python 3.12: the whole validate-nvx unittest list (1,365 tests), test_adversarial.py, test_hosts.py, the nvx.py help commands, and the link check. The publish step's tests run against a local bare repository, including a rejected push and a rerun after newer inputs. The regression tests of the three review fixes fail without their fix.
  • The head, on Windows: the whole validate-nvx unittest list. Five tests of test_setup_azure.py, which run the Linux setup scripts with bash, fail on this host, where bash is WSL's, and fail the same way on dev; the Windows CI job runs them with Git Bash.
  • scripts/module_diagrams.py --output doc/modules in a container that mirrors the draw job reproduces doc/modules byte for byte, run after run, and the first CI run of the draw job drew the same bytes.
  • Left to CI: the GitHub-hosted jobs, and the first run of the draw job on a GitHub runner. The publish job runs only after the merge. The change selects no hypervisor suite.

The nvx crate holds more than 200 modules across the host, the guest,
and the code that they share, and nothing showed how they fit together.
module_diagrams.py draws them with cargo-modules and Graphviz. It
exports the modules of the crate's library, with its cli, testing, and
guest features, for x86_64-unknown-linux-musl, the guests' target, and
for x86_64-pc-windows-msvc, and merges the two exports into one set of
diagrams: the top-level modules, with what each holds and how many
modules it contains; the dependencies between top-level modules,
counted by the pairs of their modules in which one uses the other; and
the hierarchy of each top-level module that contains modules. A module
that only one platform builds, such as the guest module or the host's
sys module on Linux, names that platform, while the color of a box
shows the part of the crate that holds the module and its border the
module's visibility. It writes the diagrams, as SVG images, with the
Markdown page that shows them, to build/modules, or to the directory
that --output names.

It replaces the PowerShell driver, scripts/module-diagrams.ps1, and the
interactive atlas, scripts/module_atlas.py, of the wip/module-diagrams
branch. A Markdown page of SVG images shows on GitHub, where the
atlas's HTML page shows only as source, so the documentation can
publish it; the page keeps the atlas's diagrams, colors, and
descriptions. The script reads its targets from the build constants,
which own the target triples, and refuses an export that holds anything
but modules, their containment, and their uses, so that a change in
cargo-modules' output cannot draw wrong diagrams silently. It replaces
an output directory only when the directory holds nothing but the page
and the diagrams of an earlier run, whose first line marks the page, and
never writes through a symbolic link. When cargo-modules is missing, it
names the release to install, 0.27.0, which builds with the Rust release
that rust-toolchain.toml pins.

Unit tests cover how it reads exports, written here in cargo-modules'
DOT form, how it merges the platforms' modules, the graphs and the page
that it draws, how it replaces an earlier run's output, and the commands
that a drawing runs, with a stand-in for cargo-modules and Graphviz.
validate-nvx runs them on Linux and Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
doc/modules publishes the module diagrams of the nvx crate that
scripts/module_diagrams.py draws: the overview of its top-level
modules, the dependencies between them, and the hierarchies of the
eight top-level modules that contain modules, as SVG images on a
Markdown page that GitHub renders. The project README lists the page
beside the project structure, and the crate's README and the project
structure link to it.

They were drawn on Ubuntu 24.04 with cargo-modules 0.27.0, built with
the Rust release that rust-toolchain.toml pins, and Graphviz 2.43.0,
which measures their labels with Liberation Sans, the first font that
they name. Drawing them again there writes the same bytes, while other
Graphviz releases and fonts lay them out differently.

A test checks that doc/modules holds the page, whose first line marks
it as the script's, and exactly the diagrams that the page shows, each
an SVG document, so that no diagram that the page stopped showing
lingers and none that it shows is missing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The module diagrams that doc/modules publishes go stale as soon as the
nvx crate changes. The module diagrams workflow draws them again after
every push to dev that changes their inputs, nvx-rs/,
rust-toolchain.toml, the script, the build constants that name the
crate's targets, or the workflow, as the merge of such a pull request
does, and commits them to dev when they change.

Its draw job runs on ubuntu-24.04 and installs Graphviz and Liberation
Sans, the first font that the diagrams name, so that the Graphviz
release and the font that lay them out stay those that drew
doc/modules: other releases and fonts lay them out differently. It
builds cargo-modules 0.27.0 with the Rust release that
rust-toolchain.toml pins, into a cache that only dev pushes save. It
checks out without credentials the newest commit of the branch that a
push or a dispatch names, rather than the commit that started the run,
draws the diagrams into doc/modules, summarizes the files that they
change, uploads them as an artifact, and names the commit that it drew
in its outputs.

The publish job runs only for dev. It checks out the newest dev with the
deploy key with which CI persists the performance history, since deploy
keys may push to dev past its rules, replaces doc/modules with the
artifact, and, when that changes anything, commits it as
github-actions[bot] with a message that names the drawn commit and
skips CI, and pushes it, retrying on the newest dev when dev moved. It
builds and runs nothing of the commit, refuses an artifact that holds
anything but the page and SVG files, or a malformed drawn commit, and
commits nothing outside doc/modules.

Runs on dev wait for one another, and a newer push replaces the run
that waits; since each run draws the newest dev, whichever run commits
last commits the diagrams of the newest dev. A ready pull request that
changes the inputs runs the draw job alone, so that a change that breaks
the drawing fails before it merges, and its summary and artifact show
the diagrams that its merge will commit. The workflow is not part of the
required status check.

The page now says that CI keeps the diagrams current, and links to the
new Module diagrams section of doc/ci.md, which describes the workflow
and how to draw the diagrams locally. Tests check the workflow's
triggers, credentials, concurrency, and pins, and the handoff of the
drawn commit and its diagrams to the publish job, and, on POSIX hosts,
run the publish step against a local repository: it commits changed
diagrams, commits nothing for unchanged ones, retries a rejected push on
the newest dev, and refuses a malformed artifact or drawn commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Incorrect platform filtering mislabels modules, and publication-only reruns can overwrite newer diagrams with stale artifacts.

2 open issues
What changed in this PR

Publishes the nvx crate’s module diagrams as GitHub-rendered documentation and adds automation to refresh them when their inputs change.

Changes:

  • Adds a Python generator that merges Linux and Windows module exports into SVG diagrams and a Markdown page.
  • Publishes the diagrams and links them from existing documentation.
  • Adds drawing and publication jobs, plus generator and workflow tests.
File Description
scripts/​test_module_diagrams.py Tests generation, published files, and workflow behavior.
scripts/​module_diagrams.py Generates cross-platform diagrams and their page.
README.md Links to the module diagrams.
nvx-rs/​README.md Links crate readers to the diagrams.
doc/​project-structure.md Documents the generator, workflow, and output directory.
doc/​modules/​README.md Presents the diagrams and explains their scope.
doc/​modules/​overview.svg Shows top-level modules.
doc/​modules/​nvx-testing.svg Shows the testing module hierarchy.
doc/​modules/​nvx-protocol.svg Shows the protocol module hierarchy.
doc/​modules/​nvx-net.svg Shows the network module hierarchy.
doc/​modules/​nvx-gpt.svg Shows the GPT module hierarchy.
doc/​modules/​nvx-exec.svg Shows the execution module hierarchy.
doc/​modules/​nvx-activation.svg Shows the activation module hierarchy.
doc/​modules/​dependencies.svg Shows dependencies between top-level modules.
doc/​ci.md Documents diagram generation and publication.
.github/​workflows/​module-diagrams.yml Draws diagrams and publishes updates to dev.
.github/​actions/​validate-nvx/​action.yml Adds diagram tests to Linux and Windows validation.

Review effort: Balanced

Comment thread .github/workflows/module-diagrams.yml
Comment thread scripts/module_diagrams.py
Unless it analyzes with cfg(test), cargo-modules drops as a test module
every module whose cfg holds when test holds and every other condition
fails: has_test_cfg, in its analyzer.rs, folds the whole condition with
test true and every other condition false, so that cfg(not(windows))
reads as a test. The pinned 0.27.0, 0.28.0, and its main branch all do.
The Linux export therefore lacked nvx::host::sandbox::scratch::imp,
which builds under cfg(not(windows)), and the host diagram labeled the
module of that path that Windows builds "Windows only", though both
platforms build one.

The script now also exports each target with cfg(test), and reads the
cfg conditions of each module from cargo-modules' structure. A module
whose conditions, or those of a module that contains it, include test
builds only for tests. Every other module of the analysis with
cfg(test) builds outside tests, so the script restores those that the
plain analysis lacks, with their uses in the analysis with cfg(test).
It stops at a condition that depends on test in another way, such as
any(test, unix) or not(test), whose modules it cannot tell apart, at a
module that only the plain analysis holds, and at a structure that it
cannot read or that names other modules than the analysis.

For the crate as it is, it restores scratch::imp alone, with its four
uses, which are also the only uses that the analysis with cfg(test)
adds between modules that build outside tests; Windows lacks nothing.
Drawn again, the host diagram no longer labels scratch::imp, and the
other diagrams and the page stay the same. Tests cover the reading of
the structure, the restoration and its refusals, and a drawing in which
each platform builds a module of the same path and cargo-modules drops
Linux's.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rerunning a run's failed jobs reruns its publish job alone, which reuses
the run's drawing and the commit that it drew. When a newer run had
published the diagrams of inputs that a newer commit changed, such a
rerun put the older diagrams back on dev, in a commit whose message
skips CI, so that nothing drew the newer ones again until the inputs
changed once more.

On every attempt, once it fetches dev, the publish job now compares the
inputs at the drawn commit and on the newest dev, as the paths that
start the workflow name them, which a test keeps equal to the job's. When
dev changed them, it publishes nothing and says why, since the push that
changed them started a run that publishes their diagrams; when dev
changed other files only, it publishes the drawing as before. A drawn
commit that the repository lacks fails the comparison, and the job.

Tests run the publish step against a local repository: a rerun after a
newer commit changed an input leaves dev untouched, the drawing still
lands after a newer commit that changes no input, and an unknown drawn
commit fails. doc/ci.md describes the comparison.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Direct publication using a ruleset-bypassing deploy key needs human review, and dependency counting has an unresolved correctness issue.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread scripts/module_diagrams.py Outdated
cargo-modules drops every module whose cfg holds when test holds and
every other condition fails, such as one under cfg(not(windows)),
unless it analyzes with cfg(test), so module_diagrams.py restores those
modules from its analysis with cfg(test). But cargo-modules' filter
(filter.rs) does not drop the uses of a module that it drops: it moves
them, and the uses of the modules that use it, to the nearest module
that contains it and that the analysis keeps, and drops those that this
turns into a module's use of itself. restore() added the restored
modules' own uses to all the plain uses, so a use that only a restored
module made counted twice: once as that module's, and once as the use
that cargo-modules had moved to the module that contains it. The
dependencies diagram, which counts the pairs of modules in which one
uses the other across top-level modules, would show 2 where 1 is right.

restore() now projects each use of the analysis with cfg(test) between
modules that build outside tests: it maps each end to itself, or to the
nearest module that contains it and that the plain analysis keeps. It
keeps the use if its projection is a plain use, or a module's use of
itself, which filter.rs drops, and marks that plain use as moved. The
uses are those, with the plain uses that no projection lands on, such
as those of an item under cfg(not(test)). A moved plain use stays only
if the analysis with cfg(test) holds it too, as when the module that
contains a restored module makes the same use itself. The uses that
only tests make stay out, as before. Where a use that involves a
restored module lands on a plain use, the two analyses cannot tell
whether only tests make the first, or whether only an item under
cfg(not(test)) makes the second; the docstring says so.

The published diagrams do not change. The only restored module,
nvx::host::sandbox::scratch::imp on Linux, uses the module that
contains it, a use that filter.rs drops rather than moves, and
nvx::error, nvx::host::seal and nvx::host::sandbox::mount::request,
which that module uses itself. So the analysis with cfg(test) holds
each moved use, and both versions of restore() give the same uses on
Linux and on Windows, as a redraw of doc/modules confirms.

A regression test has the plain analysis hold host's use of protocol,
which only the restored module imp makes, and checks that the use
counts once, as imp's, while the use of error, which host and imp both
make, counts twice. It fails with the previous union. Two more tests
pin that the plain uses that no projection explains stay, and that the
uses that only tests make between modules that the plain analysis keeps
stay out. doc/ci.md now says that the script restores those modules
with their own uses in place of the ones that the plain analysis moves.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The publication job uses a deploy key that bypasses dev protections, warranting final human review of its privileged access.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) merged commit 2422c6e into dev Oct 27, 2026
40 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/rebase-module-diagrams-for-docs branch October 27, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants