Skip to content

nvx-rs: add the template store - #881

Open
Enrique Saurez (esaurez) wants to merge 1 commit into
esaurez/839-pr1-compatibility-key-20261024from
esaurez/839-pr2-template-store-20261024
Open

Enrique Saurez (esaurez) wants to merge 1 commit into
esaurez/839-pr1-compatibility-key-20261024from
esaurez/839-pr2-template-store-20261024

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #879; this PR retargets to dev when the pull requests below it merge. The chain on dev is #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:

<dir>/                   the store, private to the current user
  store.json             the store's identity record, which its creation writes last
  index.json             the index of the generations that the store holds
  index.lock             the lock that serializes the transactions of the index
  generations/           the payloads of the generations, which never change once indexed
    <G>/                 the payload of the store generation G
      key.bin            the canonical encoding of its compatibility key
      snapshot/          its snapshot, as OpenVMM published it

Store::create creates 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 with ForeignFiles. Store::open opens an existing store. A store has an identity, a StoreId of 16 random bytes, which its identity record and its index both hold, so that the index of another store is refused.

The Index lists the generations in the order of their publication, at most MAX_GENERATIONS (1024). Each Generation holds 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_dir and Store::read_key check a payload as they read it.

The records are JSON in one format, STORE_FORMAT 1, 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:

  • the store generation, nvx-rs: define the activation record of a layered root and its limits #871's 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 a TemplateReference;
  • the bound-block storage generation, StorageGeneration, 16 random bytes that belong to the storage owner, which generations may share;
  • the time lineage counter, TimeContract::lineage, which the clones of one generation share;
  • the VM generation ID, which each VM process draws, and which the store never records.

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: platform gains create_owner_only_dir and check_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

  • The adapter chooses the store's location, whose parent directory must exist.
  • A store holds at most 1024 generations, which bounds the index's size.
  • A store of another format is replaced by a new one, whose templates are captured again, rather than migrated.
  • A StorageGeneration is 16 random bytes that StorageGeneration::generate draws for a capture; the storage owner keeps the blocks that it binds immutable.

Validation

At 867090f, the head, restacked on #879's ff7ddec, on Windows and on Linux x86_64:

  • cargo fmt --check; cargo clippy with --all-targets --all-features and --lib --no-default-features, -D warnings; cargo test with all, default, and no features; cargo doc with -D warnings, also of private items on Windows; cargo +1.89 check;
  • tests/openvmm_vectors.rs with --ignored, against the OpenVMM submodule at its pin, a12d916;
  • the runtime adapter's protocol-only and host-only configurations, with their MSRV checks, and, on Linux, its guest-only configuration;
  • the checks of aci_edge_sandboxes, which builds on nvx: cargo fmt --check, cargo clippy with all features and without default features, cargo test with all and default features, the agent's bundling, cargo doc, and its MSRV check, and, on Linux, its macOS check;
  • the tests of the bundled artifacts of nvx, against a synthetic bundle;
  • the template and sandbox-store tests repeatedly: twice more on Windows and three times on Linux;
  • on Linux, also the macOS check, and the guest binaries' check, build, and tests.

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.

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 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

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 new persistence layer combines crash recovery, concurrent locking, and cross-platform filesystem privacy guarantees that warrant final human review.

No open issues 🎉

Review effort: Balanced

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

Cross-platform persistence, locking, privacy checks, and interruption recovery warrant final human review despite no confirmed blocking defects.

No open issues 🎉

Review effort: Balanced

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

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

Cross-platform persistence, interrupted-creation recovery, locking, and filesystem privacy guarantees warrant final human review.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
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