Repository navigation
nvx-rs: add the template store - #881
Open
Enrique Saurez (esaurez) wants to merge 1 commit into
Open
Enrique Saurez (esaurez) wants to merge 1 commit into
Enrique Saurez (esaurez) wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The cross-platform persistence, recovery, locking, and access-control guarantees warrant final human review.
No open issues 🎉
What changed in this PR
Adds nvx::host::template::Store, the storage foundation for #839 and later template publication in #841. It keeps template generations separate from sandbox records; capture and publication remain out of scope.
Changes:
- Adds store identity, a versioned generation index, payload access, and serialized creation.
- Reuses atomic record writes and adds private-directory checks and shared bounded file reads.
- Adds tests for recovery, concurrency, record validation, privacy, and link rejection, plus public documentation.
| File | Description |
|---|---|
nvx-rs/src/lib.rs |
Adds the template store to crate documentation. |
nvx-rs/src/host/template/store/tests.rs |
Tests store lifecycle, records, locking, and privacy. |
nvx-rs/src/host/template/store.rs |
Implements the store, index, identities, and payload access. |
nvx-rs/src/host/template/mod.rs |
Exports and documents the store API. |
nvx-rs/src/host/template/manifest.rs |
Uses the shared bounded file reader. |
nvx-rs/src/host/template/files.rs |
Provides bounded reads that reject links and non-regular files. |
nvx-rs/src/host/store/mod.rs |
Makes existing file helpers available within the crate. |
nvx-rs/src/host/store/files.rs |
Exposes shared locking and atomic-write helpers. |
nvx-rs/src/host/platform/windows.rs |
Reuses sandbox directory privacy checks for templates. |
nvx-rs/src/host/platform/other.rs |
Adds unsupported-platform directory helper stubs. |
nvx-rs/src/host/platform/linux.rs |
Adds owner-only directory checks while preserving sandbox requirements. |
nvx-rs/src/host/mod.rs |
Documents the template store in the host module. |
nvx-rs/README.md |
Describes store layout, guarantees, and generation namespaces. |
Review effort: Balanced
This was referenced Oct 24, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/839-pr2-template-store-20261024
branch
from
October 24, 2026 22:20
401015b to
8718d50
Compare
This was referenced Oct 25, 2026
This was referenced Oct 25, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/839-pr1-compatibility-key-20261024
branch
from
October 27, 2026 02:56
8ef07da to
01aa995
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/839-pr2-template-store-20261024
branch
from
October 27, 2026 02:56
8718d50 to
95a7e95
Compare
Templates need a store of their own, apart from every sandbox's store, whose generations clones lease and restore, at a location that the adapter chooses. Add nvx::host::template::Store: a private directory with an identity of its own, the immutable payload of each generation, its key's canonical encoding beside the snapshot that OpenVMM published, and a versioned index that lists each generation with the identities of its key and its capture class and its own metadata, such as its time contract, which eligibility and restores read without the payload. Creation is crash-safe, makes one store between concurrent creations, and refuses a directory of anything else. Records are JSON in one format, replaced with the sandbox store's atomic writes, and records of another format fail rather than being migrated. The store's directories must be private to the current user, through the sandbox store's checks with a wording of their own on Linux, and links, and on Windows other reparse points, are refused among its directories and files. Four namespaces stay apart, each with a type of its own: the store generation, nvx::activation::StoreGeneration, which the index assigns in the order of publication and never twice, and by which a clone's activation record names its template; the bound-block storage generation, which belongs to the storage owner; the time lineage counter of the time contract; and the VM generation ID, which the store never records. Publication, which captures a template and indexes it, builds on the store, and the manifest reader now shares the store's bounded reads of regular files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc335726-838c-4538-a3c4-d6791aba5101
Enrique Saurez (esaurez)
force-pushed
the
esaurez/839-pr1-compatibility-key-20261024
branch
from
October 27, 2026 04:39
01aa995 to
ff7ddec
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/839-pr2-template-store-20261024
branch
from
October 27, 2026 04:39
95a7e95 to
867090f
Compare
This branch has not been deployed
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 #879; this PR retargets to dev when the pull requests below it merge. The chain on
devis #883 → #876 → #871 → #873 → #874 → #879 → #881 → #885 → #886, after #863, #864, #865, #866, #867, #868, #869, #870, #872, #875, #878, and #882, which have merged.Clones lease and restore the generations of templates that a host publishes, and that it later retires. Those generations need a store of their own, apart from every sandbox's store, whose index tells which generations it holds, and what eligibility checks and restores need to know of each, without reading its payload. This adds
nvx::host::template::Store: a private directory at a location that the adapter chooses, with an identity of its own, the immutable payload of each generation, and a versioned index of them. It changes no behavior: nothing publishes to a store yet. Publication, #841, builds on it.Part of #839: the second of its four pull requests. The first, #879, adds the compatibility key; the third adds eligibility, leases, tombstones, and retirement, and the fourth the supervisor seam and the lock order.
What changed
The store (
template/store.rs). A store's directory holds:Store::createcreates a store, or opens the one that its directory already holds. It writes the identity record last, so an interrupted creation leaves no store, and a later one replaces it. Concurrent creations take the index's lock and make one store between them, and a directory with any other entry is refused withForeignFiles.Store::openopens an existing store. A store has an identity, aStoreIdof 16 random bytes, which its identity record and its index both hold, so that the index of another store is refused.The
Indexlists the generations in the order of their publication, at mostMAX_GENERATIONS(1024). EachGenerationholds the identities of its key and of its capture class, its storage generation, and its own metadata from its manifest: its creation time, its time contract, and the lengths of its artifacts, which eligibility checks and restores read without the payload.Store::snapshot_dirandStore::read_keycheck a payload as they read it.The records are JSON in one format,
STORE_FORMAT1, replaced with the sandbox store's atomic writes (a temporary file, flushed, then renamed) under its lock files. A record of another format fails with a message that names its format, and is neither read nor migrated.Four namespaces, each with a type of its own:
nvx::activation::StoreGeneration, the host's publication ID: the index assigns it from 1 on, in the order of publication, and never twice, even once its generation is gone. A clone's activation record names its template by it and by the key's identity, in aTemplateReference;StorageGeneration, 16 random bytes that belong to the storage owner, which generations may share;TimeContract::lineage, which the clones of one generation share;Privacy (
platform/,store/files.rs). The store's directories must be private to the current user, the orchestrator's account. On Linux, a directory must belong to the user and grant the group and others nothing, and those that the store creates have mode 0700. Unlike a sandbox store's directory, it holds no control socket, so it need not have exactly mode 0700:platformgainscreate_owner_only_dirandcheck_owner_only_dir, the sandbox store's checks without that rule. On Windows, they are the sandbox store's checks. A directory or file of the store that is a link, or on Windows any other reparse point, is refused, and so is a record that is not a regular file. The store names its files by their paths, so the directories above it must be ones that only the user, or an administrator, may change, as the sandbox store requires of its own.Shared reads (
template/files.rs). The manifest reader and the store share one bounded read of a regular file that is not a link or other reparse point.Assumptions to review
StorageGenerationis 16 random bytes thatStorageGeneration::generatedraws for a capture; the storage owner keeps the blocks that it binds immutable.Validation
At
867090f, the head, restacked on #879'sff7ddec, on Windows and on Linux x86_64:cargo fmt --check;cargo clippywith--all-targets --all-featuresand--lib --no-default-features,-D warnings;cargo testwith all, default, and no features;cargo docwith-D warnings, also of private items on Windows;cargo +1.89 check;tests/openvmm_vectors.rswith--ignored, against the OpenVMM submodule at its pin,a12d916;aci_edge_sandboxes, which builds onnvx:cargo fmt --check,cargo clippywith all features and without default features,cargo testwith all and default features, the agent's bundling,cargo doc, and its MSRV check, and, on Linux, its macOS check;nvx, against a synthetic bundle;The store's tests cover creating, opening, and reopening a store, an interrupted creation, foreign entries, and concurrent creations, also one that another overtakes; the records' JSON form, records of other formats, and malformed indexes and those of other stores; the four namespaces; the checks of payloads; the index's lock; links, refused, however their paths end: symbolic links on Linux, and on Windows junctions, and symbolic links where the host lets the test create them; records that are not regular files; and the privacy of the store on each platform.
Every check passed on both hosts.
A final review by a separate model found two issues, which the head addresses: a creation that finds no store, and then the entries of one that a concurrent creation made meanwhile, now opens that store rather than failing with
ForeignFiles; and the store drops the trailing separators of its path, so that it refuses a link at its own directory however the path ends, since Linux follows a link at a name that a separator ends.The restack on #874, through #879, replayed this pull request's commit without conflicts, and changed no code. A final review of the restack by a separate model found no issues.
A second restack, on #874's
fa45e48, which drops the configuration identity of activation records, through #879, replayed this pull request's commit without conflicts, and changed no code. A final review of it by a separate model found no issues.NVX CI does not run until this pull request targets
dev.