Conversation
Completes the standalone metadata path: give it an RSF and it answers
Versions and Metadata with no network and no database.
VALIDATED END TO END ON THE REAL PRODUCTION CORPUS. Walking outward from
flask, the layer reached 131 packages at depth 3 via 260 index calls in
34.9 ms, and every dependency name parsed out of one package's metadata
resolved to a real package in the index -- zero unresolvable names. That is
the actual precondition for a solver, and synthetic fixtures cannot establish
it: it proves requirements parse, names normalize consistently on both the
producing and consuming side, and lookups compose transitively on real data.
Also a useful figure against the #18651 gate (<100 ms cold): the metadata
layer is nowhere near the budget.
Files always returns the new ErrFilesUnavailable. An RSF carries no filename,
hash, upload time, or yanked flag, so there is nothing to report. A distinct
sentinel rather than reusing ErrMetadataUnavailable because the two call for
different handling: this one means "ask another source", the other means
"choose another version". Returning an empty slice would assert something
false -- that the version ships no files.
Two deliberate asymmetries in how bad producer data is handled, both tested:
* An unparseable REQUIREMENT fails the lookup. Skipping it would hand the
resolver an incomplete dependency set and produce a confidently wrong
answer, which is the one failure mode worth being loud about.
* An unparseable requires_python leaves the constraint unset. That
over-admits a candidate and surfaces as an install-time failure, whereas
dropping a requirement would silently change the resolution itself.
Similarly, a version key PEP 440 rejects is skipped rather than failing the
package, so one bad key cannot hide every other version; and a version absent
from the deps blob reports ErrMetadataUnavailable rather than
ErrPackageNotFound, since the package is genuinely present and reporting
not-found would invite a resolver to treat it as a typo.
Extras are PEP 685-normalized on the way out so pkg[Test-Suite] matches a
declared test_suite. Version lookup falls back to PEP 440 equality when the
producer's key spelling differs from the request ("1.0" vs "1.0.0").
Decoded deps are cached per package, because a resolver calls Versions once
and then Metadata per candidate -- without it every call would re-read and
re-decompress the same blob. The cache is deliberately unbounded: a
resolution touches its closure, so it is bounded by requested work rather
than by the corpus. A long-lived server resolving arbitrary requests would
want a bound; that is not this consumer. Copy-on-return, and a -race test,
because the cached map is shared behind the interface's concurrency promise.
Verified: 97.7% statement coverage on index/; -race clean; gofmt silent;
golangci-lint v2.11.2 reports 0 issues from the module root. doc.go rewritten
to match reality -- it had reverted to its pre-#18647 text when the
CachedJSONIndex PR was closed.
Refs rstudio/package-manager#18647
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
RSFIndex— completes the standalone metadata path. Give it an RSF file and it answersVersionsandMetadatawith no network and no database.Validated end to end on the real production corpus
The unit tests use synthetic fixtures; this is the part that actually matters. Walking outward from
flaskthrough the whole layer:with zero unresolvable names — every dependency name parsed out of one package's metadata resolved to a real package in the index.
That's the genuine precondition for a solver, and fixtures can't establish it. It proves requirements parse, names normalize consistently on both the producing and consuming side, and lookups compose transitively on real data. Also a useful figure against #18651's
<100 ms coldgate: the metadata layer is nowhere near the budget.Filesreturns a new sentinelAn RSF carries no filename, hash, upload time, or yanked flag, so
Filesalways returnsErrFilesUnavailable.Distinct from
ErrMetadataUnavailableon purpose, because the two call for different handling: this one means "ask another source", the other means "choose another version". Returning an empty slice would assert something false — that the version ships no files.Two deliberate asymmetries in handling bad producer data
Both tested, because the reasoning is what makes them right rather than arbitrary:
requires_pythonleaves the constraint unset. That over-admits a candidate and surfaces as an install-time failure, whereas dropping a requirement would silently change the resolution itself.Same reasoning elsewhere: a version key PEP 440 rejects is skipped rather than failing the package, so one bad key can't hide every other version. And a version absent from the deps blob reports
ErrMetadataUnavailable, notErrPackageNotFound— the package is genuinely present, and not-found would invite a resolver to treat it as a typo and give up.Other behavior
pkg[Test-Suite]matches a declaredtest_suite.1.0vs1.0.0).Versionsonce thenMetadataper candidate; without it every call would re-read and re-decompress the same blob. The cache is deliberately unbounded: a resolution touches its closure, so it's bounded by requested work rather than by the corpus. A long-lived server resolving arbitrary requests would want a bound — that isn't this consumer, and the doc comment says so. Copy-on-return plus a-racetest, since the cached map is shared behind the interface's concurrency promise.Testing
index/;-race -count=1clean;gofmt -l .silent;golangci-lint run ./...at CI's pinned v2.11.2 → 0 issues from the module root.TestRSFIndexAgainstRealFileis opt-in viaPYPIRSF_TEST_FILE.pypirsf's layout structs, so framing comes from the code that produces production files.Also rewrote
index/doc.go, which had reverted to its pre-#18647 text when theCachedJSONIndexPR was closed and so still described implementations that aren't coming.Next
A thin CLI, so there's a runnable artifact. Worth being explicit that actual resolution is gated on
go-pubgrub, which does not exist yet (Phase 4, #18653) — everything so far is the metadata layer it will sit on.🤖 Generated with Claude Code