feat(check): materialize absent Yarn candidates in an isolated mirror (v2.11.0) - #18
Merged
Merged
Conversation
v2.10 could only judge a package already present in the project's
yarn.lock, so checking a dependency before adding it fell to
project-closure-unavailable and became deny-only. That blocked the
normal release path for a new Yarn dependency even when the project's
own resolutions would have resolved it safely.
When the requested locator is absent, safedeps now builds a private
mktemp mirror containing only the canonical resolution inputs (root and
workspace package.json, yarn.lock, .yarnrc.yml, .yarn/releases|plugins|
patches), adds the candidate to the mirror manifest alone, and resolves
it with `yarn install --mode=update-lockfile --no-immutable`. That mode
skips the link step, so no candidate lifecycle script runs. node_modules,
caches, unplugged packages, install state and VCS data are never copied.
The caller's tree stays read-only. Project inputs are re-hashed before
and after the Yarn run, so a concurrent manifest/lockfile edit
invalidates the candidate instead of approving a mixed project state.
The approval binds its provenance: project_context becomes
yarn-project-materialized-lockfile and carries materialization
{candidate, input_sha256, generated_lockfile_sha256, command, isolation}.
Ledger validation requires those fields and rejects a materialization
input hash that disagrees with the context hash.
Every failure path (input copy, context drift, Yarn invocation,
unresolvable candidate) denies with
project-candidate-materialization-unavailable. There is no
published-closure fallback.
Cover the private-mirror-only materialization, the canonical input set, the input/generated-lock provenance binding, the fail-closed denial with no published-closure fallback, and caller-tree invariance across the English SSoT and the Korean mirrors. ROADMAP had no v2.10 section at all (the previous release bumped only the current-version line), so this adds both the missing v2.10 resolution-aware entry and the new v2.11 entry, in both languages.
…rn call CI caught this on ubuntu while macOS passed. The caller-project assertion compared the fixture's logical path against the $PWD the Yarn stub logs, which safedeps has resolved with `pwd -P`. On macOS TMPDIR lives under the /var symlink, so the two strings never matched and the assertion passed without testing anything. On Linux /tmp is a real directory, the strings matched, and it failed. The assertion was also wrong on its own terms. Locator discovery runs `yarn info` in the caller project by design -- that is the v2.10 project-closure read -- so forbidding every Yarn invocation there contradicts the feature. What must never happen is a *mutating* command in the caller. Compare physical paths and assert the real property: no install-class command runs with the caller as cwd, and the update-lockfile install does run somewhere that is not the caller. The pair cannot both hold unless the materialization was isolated, so neither half can pass vacuously. Verified by mutation: forbidding `info` in the caller now fails on both macOS and Linux path shapes (the old assertion failed only on Linux), and requiring the install to run in the caller fails as well.
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.
Problem
v2.10 resolved a Yarn Berry project's closure from its actual
yarn.lock, but only for a package already in that lockfile. Checking a dependency before adding it — the case the gate exists for — hitproject-closure-unavailableand became deny-only. The normal release path for a new Yarn dependency was blocked even when the project's ownresolutionswould have resolved it safely.This was the reproducible reason the validator rejected the v2.10 plan.
Approach
When the requested locator is absent from the project lockfile, resolve the candidate in an isolated mirror instead of denying or falling back to the published closure.
mktempcopy of the canonical resolution inputs only: root + workspacepackage.json,yarn.lock,.yarnrc.yml,.yarn/{releases,plugins,patches}.node_modules, caches, unplugged packages, install state, and VCS data are never copied.yarn install --mode=update-lockfile --no-immutable. That documented mode skips the link step, so no candidate lifecycle script runs.resolutions/config/lockfile edit invalidates the candidate rather than approving a mixed project state.project_context.typebecomesyarn-project-materialized-lockfileand carriesmaterialization { candidate, input_sha256, generated_lockfile_sha256, command, isolation }.safedeps_ledger_validate_jsonrequires every field and rejects an entry whosematerialization.input_sha256disagrees with the contextinput_sha256.project-candidate-materialization-unavailable. The published closure is never used as a substitute.Approval truth is therefore neither a registry probe nor the caller's stale lockfile: it is the Yarn resolution derived from a hash-bound copy of the caller's own inputs.
Verification
npm test(smoke + e2e) green, including the newYarn absent candidates materialize only in an isolated mirror with bound provenancenext@16.2.11approves only when the isolated closure resolvessharp@0.35.3+postcss@8.5.21; the unpatchedsharp@0.34.5/postcss@8.4.31closure deniesnode_modulesasserted absent from copied mirror inputsbash -non all touched scripts,git diff --check, version SSoT 2.11.0 (package.json==SAFEDEPS_VERSION),safedeps scan secrets --repocleanAGENTS.md: no Korean prose in English docs, English/Korean mirrors symmetricInvariants held
Zero npm dependencies. OSV remains the single canonical advisory truth. effect-primary stays npm-only. No silent fallback — every failure path is an observable deny.
Docs
English SSoT + Korean mirrors updated (README, ARCHITECTURE, ROADMAP, SKILL). ROADMAP had no v2.10 section at all — the previous release bumped only the current-version line — so this adds the missing v2.10 entry alongside v2.11 in both languages.