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