Repository navigation
nvx-rs: define the activation record of a layered root and its limits - #871
Merged
Pedro Henrique Penna (ppenna) merged 3 commits intoOct 27, 2026
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Workload identity validation accepts u32::MAX, which the existing guest entry rejects.
1 open issue
What changed in this PR
Introduces feature-independent nvx::activation types and limits for layered-root configuration, the first step of #838. Encoding, identity calculation, transport, and application remain outside this PR.
Changes:
- Defines activation records, restore references, configuration settings, and Ready metadata.
- Adds validation and tests for limits, invalid combinations, and opaque identifiers.
- Documents the module in the crate and repository guides.
| File | Description |
|---|---|
nvx-rs/src/lib.rs |
Exposes and documents the activation module without feature gates. |
nvx-rs/src/activation/tests.rs |
Tests boundaries, validation errors, record variants, and identifiers. |
nvx-rs/src/activation/mod.rs |
Defines activation types, public limits, and validation. |
nvx-rs/README.md |
Documents the activation API and feature-independent availability. |
doc/project-structure.md |
Adds activation to the repository layout. |
Review effort: Balanced
This was referenced Oct 24, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/838-pr1-activation-record-types-20261024
branch
from
October 27, 2026 01:11
dc4517f to
3032e6d
Compare
Enrique Saurez (esaurez)
changed the base branch from
dev
to
esaurez/860-pr1-resolver-runtime-binds-20261024
October 27, 2026 01:11
This was referenced Oct 27, 2026
Pedro Henrique Penna (ppenna)
force-pushed
the
esaurez/860-pr1-resolver-runtime-binds-20261024
branch
from
October 27, 2026 05:05
50f7fe1 to
f3e0317
Compare
Base automatically changed from
esaurez/860-pr1-resolver-runtime-binds-20261024
to
dev
October 27, 2026 13:03
A clone that the host restores from a template keeps the template's captured configuration and command line, so nothing tells its guest the image, upper, export, network settings, and workload view of its own launch, and nothing identifies the settings that a guest applied (#838). The host, the guest, and every adapter's framing need one record of them, which they all check by the same rules. nvx::activation defines it without features, beside nvx::protocol. An ActivationRecord holds the version of its schema, the restore section of a clone, which names the restore's transaction ID and the template that it restores, by its store generation and the identity of its key, and the Configuration that the activation applies: the image, with the identity of its binding and the UUID or disk GUID that the guest expects; the upper, a tmpfs or the scratch with its binding's identity and journal policy; the overlay mode; one export, a directory or an aggregate, with its plan's identity and its 1 to 4,096 mappings; the network, with its policy's identity, a DNS address, the listeners' publication, and a proxy endpoint; the workload view, its hostname, machine-ID mode, and optional workload identity; and the CPU count and memory size that the guest must have. The record names objects by 32-byte identities that the host computes, and never holds their content; the other inputs of the guest's root assembly are invariants of the template. ReadyActivation is what Ready reports: the configuration identity, and a clone's transaction ID. The limits are public constants: 1,024 bytes for a record, 4,096 mappings, 64 bytes for a hostname, and 255 for a proxy endpoint. ActivationRecord::new admits a configuration only once Configuration::check finds that it keeps them, pairs an export with its mappings, and holds a nonzero UID and GID, a hostname without a NUL, as the guest's entry requires, a proxy endpoint of visible ASCII without an @, and so without credentials, and nonzero sizes. The canonical encoding and the configuration identity follow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f7bd07b-b09f-48d5-bc49-ad06f47ef3f9
The kernel reads an ID of u32::MAX as "unchanged", so the guest's entry refuses it, and the managed agent ignores such a workload ID on its command line. Configuration::check refused only 0, so a record could hold a workload identity that no guest applies. It now refuses u32::MAX too, for the UID and the GID alike, and the tests take u32::MAX - 1 as the largest ID that a record holds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f7bd07b-b09f-48d5-bc49-ad06f47ef3f9
The template store publishes each template under a nonzero 64-bit ID, which it assigns in the order of publication and never reuses, and a clone's record names the template that it restores by that ID and the identity of the template's key (#838, #839). StoreGeneration held 16 opaque bytes, so the store would have had to fit its ID into bytes of another shape, by a convention of its own. StoreGeneration now wraps a NonZeroU64: StoreGeneration::new takes one, StoreGeneration::get returns it, two generations of one store compare in the order of their publication, and a generation displays as its decimal number. The identity of the template's key stays 32 bytes, and the transaction ID 16. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f7bd07b-b09f-48d5-bc49-ad06f47ef3f9
Pedro Henrique Penna (ppenna)
force-pushed
the
esaurez/838-pr1-activation-record-types-20261024
branch
from
October 27, 2026 13:17
3032e6d to
0e11a65
Compare
Pedro Henrique Penna (ppenna)
deleted the
esaurez/838-pr1-activation-record-types-20261024
branch
October 27, 2026 13:47
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.

Stacked on #876; this PR retargets to dev when the pull requests below it merge. The chain on
devis #866 → #882 → #883 → #876 → #871 → #873 → #874, after #863, #864, #865, #867, #868, #869, #870, #872, #875, and #878, which have merged.A clone that the host restores from a template keeps the template's captured configuration, so nothing tells its guest the image, upper, export, network, and workload view of its own launch, and nothing identifies the settings that a guest applied. This adds
nvx::activation, the record of those settings and its limits, without features, besidenvx::protocol, so that the host, the guest, and every adapter's framing check it by the same rules. It changes no behavior: nothing builds, carries, or applies the record yet.Part of #838: the first of its three pull requests. The second, #873, encodes the record canonically, and the third, #874, adds the encoding of Ready's activation and the conformance vectors, and removes the configuration identity, so that Ready reports only that the activation applied, and a clone's transaction ID.
What changed
The record (
nvx-rs/src/activation/mod.rs). AnActivationRecordholds the schema'sVERSION, a clone'sRestoreSection, and theConfiguration:Image: EROFS or GPT, theIdentityof the image's binding, the EROFS UUID or GPT disk GUID that the guest expects, if any, and a GPT disk's first-partition bound;Upper: a tmpfs, or the scratch, with its binding'sIdentityand itsJournalpolicy;OverlayMode;Export, a directory or an aggregate, with its plan'sIdentity, which covers the export's whole shape, and its 1 to 4,096 mappings;Network: its policy'sIdentity, an optional DNS address, the listeners'Publication, in the vocabulary ofnvx::guest::entry, and an optional proxy endpoint;WorkloadView: the hostname, theMachineIdMode, the image's own machine ID or a generated one, and an optionalWorkloadIdentity, a UID and a GID, neither 0 noru32::MAX;ExpectedResources: the CPU count and the memory size that the guest must have.A clone's
RestoreSectionholds the restore's 16-byteTransactionIdand theTemplateReference: the template'sStoreGeneration, the nonzero publication ID that its store assigns in the order of publication and never reuses, and theIdentityof the template's key. AnIdentityis 32 bytes that the host computes, such as the SHA-256 digest of an object's canonical form; the record never holds the objects that identities name.ReadyActivationis what Ready reports: theConfigurationIdentity, which #874 removes so that nothing hashes the record, and a clone's transaction ID.The other inputs of the root's assembly are invariants of the template, as the module documents: the runtime directory, the block devices' MMIO windows and sizes, the GPT partitions that the guest expects, of which there are none, and how long the guest waits for its devices. Secrets, per-execution environments, and host mapping tables never enter the record.
The limits are public constants:
MAX_RECORD_BYTES(1,024),MAX_MAPPINGS(4,096,protocol::MAX_HOST_MAPPINGS),MAX_HOSTNAME_BYTES(64), andMAX_PROXY_ENDPOINT_BYTES(255).ActivationRecord::newadmits a configuration only onceConfiguration::checkfinds that it keeps them, and otherwise reports the first one broken, in the order of the fields. Besides each limit, an export without mappings, mappings without an export, and a zero UID or GID, it refuses:u32::MAX, which the kernel reads as an unchanged ID, and which the guest's entry and the managed agent refuse;@, which would start credentials;Documentation. The module documentation describes the configuration, the template's invariants, and the limits, with an example. The crate's README and
doc/project-structure.mdlist the module.Tests (
nvx-rs/src/activation/tests.rs): each limit at and past its edge; an export without mappings and mappings without an export, refused; a UID or GID of 0 oru32::MAX, refused, each alone; a configuration with every part at its limit, admitted; cold-boot and clone records; the order in which limits are reported; the errors' messages; the opaque values' bytes and hexadecimal forms; and store generations, nonzero numbers that compare in the order of publication and display in decimal.Validation
At
3032e6d, the head, rebased onto #876 without changes to the contents of its commits, on Windows and on Linux x86_64:cargo fmt --check;cargo clippywith--all-targets --all-features,--lib, and--lib --no-default-features,-D warnings;cargo testwith all, default, and no features;cargo docwith-D warnings;cargo +1.89 check;aci_edge_sandboxes' checks;bundledartifacts test, the macOS check of both crates, the guest binaries' lint, tests, build, and check, and the Markdown link check.At each of the three commits,
cargo clippywith--all-targets --all-features,--lib,--lib --no-default-features, and--lib --no-default-features --features guest,-D warnings, passed on both systems.Before the rebase, on
dev, the same checks passed at the first and the third commits, and at the heads of #873 and #874 that contained the second, which answers the review, and CI passed at the third, in run 40121956945. At the first commit, one test outside this change failed once on Linux, in the all-features run on a busy host:guest::cgroup::tests::settlement_collects_statuses_between_its_checks_until_the_deadline, whosecollections.get() > 1assertion races a 50 ms deadline. At the same head, it passed 50 of 50 runs alone, and the all-features suite passed 3 of 3 runs. While this pull request targets #876's branch,dev's CI doesn't run on it.The third commit makes the
StoreGenerationthe nonzero number that the template store of #839 assigns to each publication, in place of 16 opaque bytes, so that a record names a template's generation in the store's own terms.The guide sections that #838 names follow the numbering of #591, which isn't on
dev, so the record is documented in the module and the crate's README.