yarn cannot install: the packument omits `time`, so yarn quarantines every version
## Problem
`yarn add` against an Artifact Registry npm repository fails at the resolution
step, for every package and every version:
```plaintext
➤ YN0016: │ dmeshcharakou-yarn-hello@npm:1.0.0: All versions satisfying "1.0.0" are quarantined
➤ YN0000: · Failed with errors in 0s 577ms
```
Nothing is downloaded, no lockfile entry is written, and `node_modules` stays
empty. This is separate from #1020: that one is the publish path, this one
remains after it is fixed, because a package published by any client cannot then
be installed by yarn.
yarn quarantines a version whose publish time it cannot establish. It reads that
time from the packument's `time` map, and the registry's packument has no `time`:
| Document | Keys the registry serves | What npm's registry also serves |
| --- | --- | --- |
| Full packument | `_id`, `_rev`, `dist-tags`, `name`, `versions` | `time`, a map of version to publish timestamp |
| Abbreviated packument (`Accept: application/vnd.npm.install-v1+json`) | `dist-tags`, `name`, `versions` | `modified`, a top-level scalar |
## Waiting does not clear the quarantine
The gate is not only about young versions. In yarn 4.18.0's bundle the check is:
```js
let gate = getMinimalAgeGate(ident, {configuration});
if (gate) {
let publishedAt = publishTimes?.[version];
if (typeof publishedAt === "undefined" ||
(new Date().getTime() - new Date(publishedAt).getTime()) / 60 / 1e3 < gate)
return true; // quarantined
}
```
The `typeof publishedAt === "undefined"` arm fires before any arithmetic, so a
version with no timestamp is quarantined regardless of its age. A registry that
serves no `time` map is therefore permanently unusable for `yarn add`, not
unusable for the first 24 hours.
`npmMinimalAgeGate` is read in minutes (`/ 60 / 1e3` converts the millisecond
delta) and yarn 4.18.0 defaults it to `1440`, so 24 hours. It was introduced in
yarn 4.10. Yarn disables it for a project whose lockfile version is below 10, so
an existing project that has not migrated its lockfile does not see this.
## Reproduction
Against staging, with a token from
`glab artifact-registry get-token --hostname staging.gitlab.com`:
```shell
# 1. The packument carries no timestamps.
curl -sSL -H "Authorization: Bearer $TOKEN" \
"$BASE/$SLUG/npm/$REPO/$PKG" | jq 'keys'
# ["_id","_rev","dist-tags","name","versions"]
# 2. yarn refuses to install, at default settings.
cat > .yarnrc.yml <<YML
npmRegistryServer: "$BASE/$SLUG/npm/$REPO"
npmAuthToken: "$TOKEN"
YML
yarn add "$PKG" # YN0016 ... All versions satisfying "..." are quarantined
# 3. The same add succeeds with the gate switched off.
echo 'npmMinimalAgeGate: 0' >> .yarnrc.yml
yarn add "$PKG" # Done
```
Observed with yarn 4.18.0 and Node 24.19.0 against
`https://staging.artifact-registry-gke.svc.gitlab.net`, repository
`dmeshcharakou-npm-hosted` in namespace `ar-registry`.
## Why the document has no `time`, and why that reason does not settle it
The omission is deliberate and specified.
[S11](../blob/main/docs/specs/S11-npm-hosted.md), `#### Packument generation`:
> The generator emits no `time` object: the cached blobs stay deterministic (see
> Async work), and `_rev` preserves that property because it derives from row
> state, not the wall clock.
and, on the rebuild job:
> Jobs are idempotent: rerunning the job on the latest state produces the same
> blob content (modulo timestamp fields — the full packument's `time` object and
> the abbreviated packument's top-level `modified` scalar — which are omitted
> from the cached blobs to keep them deterministic; the generator has no
> wall-clock or timestamp input).
The determinism requirement is real: the cached blobs are content-addressed, and
a document that changes on every rebuild would churn `repositories.size_bytes`
and defeat deduplication.
That requirement does not rule out a `time` map, by the spec's own argument for
`_rev`. A per-version publish timestamp is row state, not a wall-clock reading:
`npm_versions.created_at` already holds it, and the management API already serves
it per version (`GET …/npm/packages/{id}/versions` returns `created_at`). A
generator that reads those column values renders identical bytes on every rerun
for unchanged rows, which is exactly the property the spec protects for `_rev`.
The abbreviated document's `modified` scalar is the maximum of the same column
values, so it is row-derived too.
## Impact
yarn is one of the three major npm-ecosystem clients, and installs from the
Artifact Registry do not work with it at default settings. The failure is at
resolution, so it hits every consumer of a package, not only the publisher.
The workaround is a client-side `npmMinimalAgeGate: 0`, which asks every consumer
to turn off a supply-chain protection for their whole project (the gate exists to
blunt the window in which a compromised release is installed) because of a
property of our registry. It also cannot be scoped to the AR registry alone
without also relaxing it, since the per-scope override is a number, not a
per-registry exemption.
## What would fix it
Render the timestamps from row state in the packument generator:
- Full packument: a `time` object mapping each active version to its
`npm_versions.created_at`, and the `created`/`modified` pair npm's registry
also carries there.
- Abbreviated packument: the top-level `modified` scalar.
This needs an S11 amendment, since the spec currently states the opposite. The
amendment can keep the determinism guarantee as written by binding the values to
the columns rather than to the clock.
## Related
- #1020, yarn cannot publish. Same client, different half of the flow. Fixing
either one alone still leaves yarn unusable end to end.
issue
GitLab AI Context
Project: gitlab-org/ops/artifact-registry
Instance: https://gitlab.com
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://gitlab.com/gitlab-org/ops/artifact-registry/-/raw/main/CONTRIBUTING.md — contribution guidelines
- https://gitlab.com/gitlab-org/ops/artifact-registry/-/raw/main/README.md — project overview and setup
- https://gitlab.com/gitlab-org/ops/artifact-registry/-/raw/main/AGENTS.md — AI agent instructions
- https://gitlab.com/gitlab-org/ops/artifact-registry/-/raw/main/CLAUDE.md — Claude Code instructions
Repository: https://gitlab.com/gitlab-org/ops/artifact-registry
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD