fix(npm): emit _rev on the full packument (S11)
Stacked on !861 (merged) (the S11 spec amendment documenting the _rev contract). Review and merge bottom-up; retarget to main once !861 (merged) merges.
Context
Real-client unpublish is dead on arrival: the npm CLI reads ._rev from the full packument to build every unpublish URL (.../-rev/{rev}), and the generator never emitted it, so npm sends -rev/undefined and gets 400 rev_invalid. The receiving side already verifies {rev} against LatestNpmVersionIDByPackage (bytewise-highest active npm_versions.id), and the spec — as amended by !861 (merged) — pins _rev to exactly that value (S11, Packument generation + Deprecate and unpublish flows).
What this MR does
GeneratePackumentsemits_revas the bytewise-highest active version id on the full packument only. The abbreviated projection's top level stays fixed toname/dist-tags/versions, and a zero-version packument omits_rev(no active version means no current revision).- The datastore's
LatestNpmVersionIDByPackagedoc gains a back-reference to the Go mirror. - Tests (written first, failing without the fix): a unit test pins
_revto a statically known bytewise-max — with the highest id carrying the lowest semver, so a drift to semver-max ordering fails; the abbreviated and zero-version omission shapes are pinned in the existing allow-list and zero-versions tests; and an integration test binds the generator's_revtoLatestNpmVersionIDByPackage's answer over the same rows with the bytewise-highest row soft-deleted, so predicate drift between the SQL and its Go mirror fails a test instead of shipping.
Validation
Against npm 11.16.0 on a locally running AR: npm unpublish <pkg>@<version> (both wire steps) and npm unpublish <pkg> --force now round-trip, and the DB state matches the spec at each stage — single-version unpublish soft-deletes the version and its file, removes only the tags pointing at it, decrements versions_count, and rebuilds the metadata cache; whole-package unpublish soft-deletes the package/versions/files cascade and hard-deletes npm_tags and npm_metadata_files; blob and attachment rows survive both flows (physical deletion stays with lifecycle/GC per ADR 011).
Deploy note: full-packument blobs cached before this change carry no _rev until their cache entry expires or a write rebuilds it, so unpublish against a warm pre-fix cache stays broken for that window after deploy (bounded by npm.packument_cache_ttl).
Related to #143 (closed)