Skip to main content

Overview

This library provides the infrastructure for monitoring the correctness of the NuGet V3 package metadata pipeline. It continuously watches the NuGet catalog (the append-only transaction log for all package events) and validates that what each V3 endpoint exposes for a given package matches the authoritative state in the gallery database. When inconsistencies are found, the package’s monitoring status is recorded as Invalid in Azure Blob Storage so that operators can investigate. The core flow has two independent phases. First, a ValidationCollector walks the catalog index from a durable cursor, batches catalog entries by package identity (using the inherited SortingIdVersionCollector), and enqueues PackageValidatorContext messages into a storage queue. Second, a PackageValidator dequeues those messages and runs all registered IValidator implementations against the package by comparing data fetched from the V2 database feed with data fetched from the V3 endpoint. Results are stored as JSON blobs in Azure Storage, partitioned by PackageState (Valid, Invalid, Unknown). The validation system is built around an endpoint abstraction. Each V3 surface (Catalog, Registration, Flat Container, and one or more Search instances) is represented as an IEndpoint with its own cursor indicating how far it has processed the catalog. The AggregateEndpointCursor combines all endpoint cursors with min semantics so the collector only processes catalog entries that every endpoint has already had a chance to ingest. Validators are scoped to their endpoint and can short-circuit via a ShouldRunAsync check that compares the database timestamp to the catalog entry timestamp, deferring validation (RetryLater) when the catalog entry is stale relative to the database.

Role in System

Catalog-Driven Collection

The ValidationCollector extends SortingIdVersionCollector to batch catalog pages by package identity and enqueue validation work items. A durable cursor stored in Azure Blob Storage tracks how far through the catalog the collector has progressed.

Endpoint-Scoped Validators

Validators are associated with a specific endpoint type (Catalog, Registration, FlatContainer, or Search) via generic typing. Autofac wires all IValidator<TEndpoint> implementations into an EndpointValidator<TEndpoint> which runs them in parallel.

Timestamp-Gated Execution

Each Validator base class compares the catalog entry timestamp against the database timestamp before running. If the catalog entry is older than the database record, validation returns RetryLater (recorded as Unknown) rather than producing a false negative.

State-Partitioned Status Storage

PackageMonitoringStatusService writes validation results as JSON blobs under state-named folders (valid/, invalid/, unknown/). When a package moves to a new state, the old blob is deleted after the new one is saved to avoid data loss.

Key Files and Classes

Dependencies

NuGet Package References

Internal Project References

Notable Patterns and Implementation Details

The Validator.ShouldRunAsync base method compares the most-recent catalog entry timestamp against the LastEditedDate from the gallery database. When the catalog timestamp is less than the database timestamp, validation returns ShouldRunTestResult.RetryLater, which is stored as TestResult.Pending and rolls up to PackageState.Unknown. This prevents false failures when the catalog pipeline is still processing a recent edit.
ValidationContext uses Lazy<Task<T>> for all metadata fetches so that each data source (database index, database leaf, V3 index, V3 leaf, timestamp) is fetched at most once per validation run, regardless of how many validators request it. Search results are cached in a ConcurrentDictionary keyed by the search base URI.
PackageMonitoringStatusService.UpdateAsync writes the new status blob before deleting the old one to avoid data loss if the delete fails. However, this means a package can temporarily appear in two state folders simultaneously. The GetAsync(FeedPackageIdentity) method handles this by selecting the status with the latest catalog commit timestamp and deleting the duplicates.
PackageIsRepositorySignedValidator and PackageHasSignatureValidator are both no-ops unless ValidatorConfiguration.RequireRepositorySignature is true. This flag allows the signature requirements to be rolled out independently of the rest of the monitoring pipeline.
Search endpoints support multiple cursor sources per named instance. Each SearchCursorConfiguration can specify either an HTTP cursor URI or an Azure Blob client. All cursors for a given search instance are combined with AggregateCursor (min-semantics), and the overall AggregateEndpointCursor takes the minimum across all endpoints, so the collector only advances as fast as the slowest endpoint.
RegistrationExistsValidator contains a known workaround: if a package does not exist in the database but a leaf node exists in V3, the validator logs an informational message and does not fail. This is documented as a known bug (NuGetGallery issue #4475) where Registration leaf nodes are not deleted after a package deletion.