Digital references fall into two families. Content-addressed references are computed from the content itself: a SHA hash of a git commit, an npm integrity hash, an OCI digest. These are unforgeable within the collision resistance of the hash function. If the content changes, the reference changes. Pinning to a content-addressed reference is structurally safe.
Name-addressed references are labels assigned by whoever owns the namespace. Git tags, npm versions, docker image tags, the literal string latest, a date-labeled artifact directory. These are mutable by design. The namespace owner (the repo maintainer, the package publisher, the registry admin) can, at any time, point the same label at different content. The pointer is metadata, not identity.
The pattern: a downstream user pins to a name-addressed reference, treating it as if it were content-addressed. The tooling does nothing to enforce the distinction. The build caches the label and the associated content together. On the next build, the label is resolved again. The resolution returns whatever the current pointer is. If the pointer has moved, the build silently picks up new code. If it has not, the build is reproducible. You cannot tell the difference from inside the build.
The attacker's position is to move the pointer. Often this requires compromising the namespace owner, which is its own pattern (maintainer-account-compromise). Sometimes it does not, because the namespace protocol allows the tag to be re-pointed without compromise (force-push, tag delete plus recreate, republish over a "yanked" version in registries that permit it). In either case, the downstream does not know. The label resolves. The build succeeds. The behavior changed.