Repository navigation
v0.11.2 — the pnpm builtin tells the truth on pnpm 12, and bounds that measure themselves
[0.11.2] - 2026-09-22
Fixed
- The pnpm builtin reported up to 165 false "missing" packages on a freshly synced pnpm-12 tree. Measured end to end on vuejs/core (660 lockfile entries,
pnpm install --frozen-lockfileexit 0 immediately before the check). Four independent causes, each found on the wild repo and each fixed against it:- Peer-suffix directory names. pnpm 11 named store dirs
name@version; pnpm 12 names them after the lockfile's snapshot key — the peer-resolved form, peers joined by_— and past 60 characters storesfirst 27 chars + '_' + sha256(key)[:32]. The recipe was proven before use (19/19 hash directories matched, zero store directories uncovered) and matching is exact again, derived from the lockfile's own spelling rather than a heuristic. - A nested YAML key hijacked the parser.
snapshots:entries indentdependencies:maps; the section-key regex let four-space lines match, so the current entry flipped to a junk key and theoptional: truethat followed landed nowhere —@emnapi/*counted missing though its snapshot marks it optional. Regexes that count spaces now count them exactly. - The package manager records itself.
packageManager: pnpm@12.4.2produces a lockfile entry for pnpm that never materializes in the project's virtual store. Same rule as platform binaries: a package not supposed to be here is not missing. - pnpm 12 filters the recorded lockfile copy.
node_modules/.pnpm/lock.yamlholds 645 of 660 entries — exactly the other-OS binaries dropped — so neither full nor machine-expected set equality holds. The check is now subset in both directions: every copied package must be in the lockfile (a foreign resolve adds packages), and every package this machine must have must be in the copy (a copy from another machine lacks them).
After the fixes the wild verdict isok: true — 489 package(s) present, matching pnpm-lock.yaml (152 platform-specific or optional skipped, 1 packageManager self-reference skipped)in 17 ms, with damage detection re-proven both ways (deletedvite@8.3.0→ flagged;pnpm install --frozen-lockfile→ restored →ok: true).
- Peer-suffix directory names. pnpm 11 named store dirs
- A check stopped by its time bound stamped
broken exit ?. Broken means the check ran and said no; a killed run never ran to a verdict. It isblocked — untested, not false, with a note naming the bound and suggestingmax_ms. Found on the same wild repo, whose suite needs ~5 minutes against the 120 s default. (The bareexit nullnote was made legible asno exit within Nsearlier in the same pass.) initdiscovered nothing on apnpm-workspace.yamlmonorepo. The earlier workspaces fix readpackage.json'sworkspaceskey; vuejs/core declares its packages in pnpm's own workspace file, so the same zero-candidates breakdown recurred on the very ecosystem this drill targets.pnpm-workspace.yamlnow counts as evidence that packages with code exist.- Sandbox teardown could crash a verify that already had its results. On Windows, an
EBUSYwhile removing the temp worktree killed the run after the check but beforestate.save()— stamp lost, worktree leaked, observed live on the wild repo. Teardown is best-effort: retries briefly, then warns and moves on, so a verify's verdict survives a dirty teardown. - Three more package-manager builtins met their wild repos; each had its own false-positive class.
- npm on npm/cli: a lockfile lists every platform's variants (
@typescript/typescript-aix-*…) butnpm cimaterializes only this machine's — 20 of 1181 "missing", every one foreign-platform. The platform rule pnpm and bun already had is npm's too now:ok: true — 1161 present, 20 platform skipped. - bun on oven-sh/bun (isolated linker): bun 1.2 suffixes a store directory with
+<hex>when the package resolves peers (@types+react-dom@18.3.7+52f32cb6c6aeed77), which exactname@versionmatching can never hit — a freshly installed healthy store read 3/23 missing. Matching folds the suffix off, raw name first so semver build metadata still matches only itself; damage/repair re-proven live (deletedtypescript@6.0.2→ named; fresh install →ok: true). - yarn 1 held up: healthy tree zero false positives, root-package damage reported rather than judged,
--check-filesrepair restoredok: true.
- npm on npm/cli: a lockfile lists every platform's variants (
inittrustednode_modulesexisting — but npm/cli commits most of itsnode_modulesto git (1367 files, no.bin), so a 122-package partial tree wired no prerequisite and the proposed suite claim ran against an install that was never made. When the tree exists, the ecosystem builtin gets the last word: a definitiveok: falsewiresrequires,ok: nullleaves discovery's judgment alone.- The git journal could silently lose an entry on a fast machine. Entry ids were second-resolution, so an accept and its immediate revert wrote the same filename — the revert overwrote the accept and the journal showed one entry where there were two. Windows passed by latency luck; the ubuntu CI runner caught it. Ids are collision-proof now.
- A vendored Go build reported "no module cache". The gate demanded
GOMODCACHEeven when the tree is vendored — correct on a dev machine, wrong on a fresh ubuntu runner. A vendored build verifies without a cache.
Added
- The repo's own manual runs in GitHub Actions on every push and PR (
.github/workflows/manual.yml), with the badge in the README — the same verify a contributor gets from the pre-commit gate, executed where nobody's laptop is involved. CONTRIBUTING.mddocuments regenerating the hero and social-preview images and keeping the two cards in sync.initcalibrates a suite's time bound instead of inventing one. A discovered command check no longer ships a hardcodedmax_ms: 120000; the probe measures the first honest run and writesmax(120000, measured × 3)rounded to seconds, withobservation.measured_msas provenance — a five-minute suite gets a bound that can hold it (vuejs/core's ~293 s → 879000) while fast suites keep today's default formanual observeto tighten later on accumulated evidence. A check that never finishes gets one 600 s calibration window, then an honestblocked. Along the way the probe adopted verify's own doctrine — a run stopped by a bound isblocked, untested not false (it used to file thembroken) — and its note stopped printing max_ms as if it were seconds (120000s).verifywaits for a claim's own bound (checkTimeoutMs): the configdefault_timeout_swas passed as an explicit timeout, which the runner prefers over every claim'smax_ms— calibrated bounds would have been decorative, a 15-minute suite still killed at 60 s. The config default now applies only to claims that declare no bound.- Regression tests for every fix above: the hash-truncated pnpm-12 directory (fixture uses the real pnpm-computed hash, not a reimplementation), the nested
optional: true, thepackageManagerself-reference, the timeout-blocked stamp, and the EBUSY-tolerant teardown (platform-aware: a live process parked in the worktree on Windows, clean removal on POSIX).
See docs/FIELD-NOTES.md, "Eighth pass" (the pnpm-12 monorepo) and "Ninth pass" (npm, yarn 1, bun, and the bound that measures itself), for the full drill narratives.