Repository navigation
Move all yarn.lock parsing into formats/yarn and stop pinning non-registry copies - #1057
Open
Mikola Lysenko (mikolalysenko) wants to merge 12 commits into
Open
Mikola Lysenko (mikolalysenko) wants to merge 12 commits into
Mikola Lysenko (mikolalysenko) wants to merge 12 commits into
Conversation
The block walk, field readers, key/descriptor/locator patterns and the classic copy-source classifier lived in the vendored backends (vendor::yarn_classic_lock, vendor::yarn_berry_lock), and the hosted rewriters, the lock inventory and VEX discovery imported them from there: the layering was inverted (E08). They now live in formats/yarn: - blocks.rs: LockBlock, scan_blocks, block_eol, replace_block, body_field_line, classic_field, berry_field, berry_metadata, live_blocks - patterns.rs: split_key_patterns, split_berry_key_patterns, split_pattern, pattern_real_name, split_resolved_sha1, BerryLocator, parse_berry_locator, resolution_selector_target - source.rs: ClassicBlockSource and the yarn 1 git classifier Every caller imports them from formats/yarn; the vendor copies are gone. Pure move: no behavior change, unit tests moved with their functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The grammar of a yarn.lock was decided three ways (B60): the vendored router and the in-memory router sniffed only the first 30 lines, every hosted rewriter, the hosted restore, VEX and the classic vendored gate scanned the whole file for `__metadata:`, and the inventory fallback ran both readers and took whichever returned entries. A classic header above a hand-merged `__metadata:` key past line 30 was classic to vendor and berry to everything else. sniff_grammar now scans the whole file, is_berry_lock wraps it, and the new grammar() reads a header-less lock as classic (what yarn 1 parses it as). The vendored router still refuses a header-less lock, since it must know the grammar it writes; the inventory fallback reads it through grammar() instead of trying both readers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hosted yarn rewriters and restorers kept their own block grammar:
seven split("\n\n") sites (rewrite_yarn_classic, berry_cache_key,
rewrite_yarn_berry_with_manifests, berry_lock_locks, berry_bin_entries,
restore_classic, restore_berry) plus regex field edits (E08).
- Classic rewrite and restore now walk the lock with scan_blocks and
splice each pinned block over its own bytes (replace_block), through
the new repin_classic_block that the vendored backend also uses. Every
untouched byte round-trips, so a lock mixing CRLF and LF lines keeps
each line's ending (the old normalize/re-expand turned the LF lines
into CRLF); only a bare CR is refused, by rewrite and restore alike.
- No regex replacement is left: a `$` in a patch-server URL was read as a
capture group by the classic rewriter (restore escaped it, the rewriter
did not). Replacements are plain line edits now.
- A classic block with no `resolved` line is left untouched; the old
rewriter still swapped its integrity for the patched sha512.
- Berry rewrite and restore share formats/yarn/stanzas.rs (BOM, line
endings, trailing newlines, sorted re-insertion), which replaces
berry_sort_key, berry_entries_sorted and berry_reposition_blocks;
berry_cache_key, berry_lock_locks and berry_bin_entries read blocks.
- classic_key_real_name replaces yarn_classic_block_head and the
vendored and VEX copies of the same "every pattern names one package"
check.
The yarn_classic_rewrite golden is re-blessed: an oracle run of the old
rewriter over its 400 seeds differed only in the edit records' leading
blank line and in the unresolved-block integrity swap above.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A classic block keyed by a `file:` tarball, a URL or a hosted-git shorthand (locked to a GitHub codeload tarball) is the project's own artifact: a fork or a local build (B16). The hosted rewriter repointed its `resolved` at Socket's patched registry artifact, silently swapping the user's code for registry bytes, and rollback then wrote the registry tarball back, losing the original `resolved`. The vendored backend did the same with the service-built tarball, and the lock inventory called codeload copies git while the rewriters called them plain tarballs. formats/yarn/source.rs now holds one classifier, CopySource (was ClassicBlockSource), which splits the old Tarball case into Registry and RemoteTarball through the shared npm_spec_is_registry rule, with a policy table for every mode: - hosted rewrite skips a RemoteTarball copy, named (redirect_yarn_classic_non_registry_skipped), and keeps it out of the in-run VEX like the git and file: directory copies; - hosted restore refuses a pin an older release wrote on one; - vendored skips it (vendor_yarn_classic_non_registry_entry_skipped) and refuses with vendor_lock_entry_not_rewritable when it is the only copy; - the lock inventory drops its registry verifiers through the same rule, replacing its own is_git_resolution. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An older release could pin a URL-keyed yarn classic block (a fork tarball) to the hosted artifact. Rollback must refuse it rather than write the registry tarball under the fork's key, and still restore the registry pin beside it (B16). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#657 added formats/yarn/berry_gates.rs with its own __metadata and field reader (metadata_fields, scalar_field), a second copy of blocks::berry_metadata and blocks::berry_field. The gates now scan the lock with scan_blocks and read cacheKey with berry_metadata and berry_field, and the private readers are gone. berry_gates::cache_key takes the scanned blocks, so the lock inventory reads the key from the blocks it already has, and the hosted rewriter's own berry_cache_key copy (dropped in the rebase) is not brought back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bare-CR line-ending check, the berry lock-locks and bin-entry block scans and the berry npm alias-target parser still lived in patch/redirect/mod.rs, and VEX discovery and the hosted engine reached is_berry_lock through a re-export there. They now live in formats/yarn (blocks.rs and patterns.rs), every caller imports them from there, and the re-export is gone, so vex and the hosted engine no longer depend on patch::redirect for a grammar question. Pure move. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Three hosted yarn classic cases gave a wrong or no answer: - A file: tarball, URL or hosted-git block an older release already pinned to this run's artifact was reported as staying unpatched and kept out of the in-run VEX, although it installs Socket's build (VEX discovery counts it as Socket's, rollback refuses it). It now gets redirect_yarn_classic_non_registry_legacy_pin, which names the pin and points to restoring yarn.lock from version control, and counts as matched. - A registry block with no resolved line was silently counted as matched with no edit, which also suppressed entry_not_found. It now gets redirect_yarn_classic_unresolved_entry_skipped and stays out of the in-run VEX. The yarn_classic_rewrite golden is re-blessed for that warning (input digests unchanged). - redirect_yarn_classic_non_registry_skipped is renamed to redirect_yarn_classic_non_registry_entry_skipped, matching npm's redirect_npm_non_registry_entry_skipped and the vendored code, before it ships. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An older release could wire a URL- or file:-tarball-keyed yarn classic block into .socket/vendor/. The new copy-source classifier saw the non-registry key and refused the block, so an in-sync vendor re-run of a project whose only copy was that block failed with vendor_lock_entry_not_rewritable and said the copy stays UNPATCHED, which is false. The classifier now checks block_points_into_vendor first: such a block stays a candidate (the re-run is a byte-stable no-op) and is named with vendor_yarn_classic_non_registry_legacy_wiring, pointing to vendor --revert. docs/ecosystems.md lists the new codes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pins two inventory changes from this branch: a file: tarball or URL fork copy carries no registry verifiers (resolved, sha1, integrity), and a header-less classic lock refused by the router is read as classic by the fallback through the one grammar decision. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hosted berry writers read the lock as blank-line stanzas and the vendored backend and field readers through scan_blocks. A test now asserts both name the same blocks in the same order across LF, CRLF, BOM, header-comment and no-trailing-newline locks, so the two reads cannot drift on any shape yarn writes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
force-pushed
the
arch-fix/yarn-grammar
branch
from
October 7, 2026 17:21
12673c1 to
209e36e
Compare
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 7, 2026 17:23
The shared redirect fixtures (npm/yarn-classic/basic, consumed by depscan's
TS golden test too) record a classic edit's original/new as the
split("\n\n") segment holding the block: a block after two blank lines
carries a leading "\n", the last block the file's final newline. The
block-scan rewriter recorded the bare block lines, failing
redirect_golden on every test leg. ClassicSegments rebuilds that segment
from separators found once per lock; yarn_classic_rewrite.golden is
re-blessed back to those records.
The scan performance check flagged yarn-classic/hosted +95% and
yarn-berry hosted/rescan +35-40%:
- classic re-scanned and re-copied the whole lock after every pinned
block; pins now live in the scanned blocks and are spliced in one pass
(splice_blocks).
- berry's per-dep version check collected every stanza's lines for every
block that did not name the dep; it now reads the field straight off
the stanza (berry_stanza_field), after the cheaper alias test.
Local compare vs 431b818 (perf profile): yarn-classic hosted +4.6%,
rescan +1.0%; yarn-berry hosted +1.6%, rescan -3.4%.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tanmay Singla (Tanmay182003)
approved these changes
Oct 7, 2026
This branch has not been deployed
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
Architecture audit items E08, B60 and B16 (theme 3.E, "JS lockfiles"):
yarn.lockhad sevensplit("\n\n")block grammars in the hosted rewriters and restorers (patch/redirect/mod.rsx5,upstream/npm.rsx2), plus regex field edits. The layering was inverted: hosted code imported the block grammar from the vendored backends. The classic rewriter passed the artifact URL toRegex::replaceunescaped, while restore escaped it. Line-ending handling also differed by path: classic hosted turned a mixed CRLF/LF lock into all-CRLF, while berry and classic restore refused it.__metadata:scan (hosted, restore, VEX, the classic gate);file:-tarball, URL and hosted-git (codeload) copies at Socket's patched registry artifact, which silently replaced the user's fork or local build. Rollback then wrote the registry tarball back and lost the originalresolved. The service-built vendored tarball did the same. The lock inventory classified codeload copies as git, but the rewriters treated them as ordinary tarballs.No issue number tracks these exactly; the nearest are #363, #857, #921 and #939.
Change
vendor::yarn_classic_lock/vendor::yarn_berry_lockintoformats/yarn/{blocks,patterns,source}.rs. Every caller imports them from there now.sniff_grammarscans the whole file andis_berry_lockwraps it. The newgrammar()reads a header-less lock as classic, which is how yarn 1 parses it. The vendored router still refuses a header-less lock, as it did before. The inventory fallback goes throughgrammar()instead of trying both readers.scan_blocksand splice each block in place (replace_block) throughrepin_classic_block. The vendored backend uses the same function.$in the artifact URL is written as-is.resolvedis left untouched. The old code swapped itsintegrity.formats/yarn/stanzas.rsfor BOM, line endings, trailing newlines and sorted re-insertion. A test asserts the stanza view andscan_blocksname the same blocks on LF, CRLF, BOM and header-comment locks.patch/redirect/mod.rs(classic_line_endings_supported,berry_lock_locks,berry_bin_entries,berry_npm_alias_target) move intoformats/yarn/{blocks,patterns}.rs. Theis_berry_lockre-export inpatch::redirectis deleted; VEX discovery and the hosted engine import it fromformats::yarn.formats/yarn/berry_gates.rswith its own__metadatareader (metadata_fields,scalar_field). Those are deleted: the gates readcacheKeythroughscan_blocks,berry_metadataandberry_field, andberry_gates::cache_keytakes scanned blocks. This PR's ownberry_cache_keyis dropped in favour of the gates, so one copy remains.CopySourceclassifier (B16).ClassicBlockSource::Tarballsplits intoRegistryandRemoteTarball, using the sharednpm_spec_is_registryrule.source.rsholds the per-mode policy table:RemoteTarballcopies, named in the new warningredirect_yarn_classic_non_registry_entry_skipped(same pattern as npm'sredirect_npm_non_registry_entry_skipped) and kept out of the in-run VEX. A copy an older release already pinned to this run's artifact getsredirect_yarn_classic_non_registry_legacy_pininstead: it installs Socket's build, so it is not called unpatched, and the warning points to restoringyarn.lockfrom version control.resolved: a registry block with noresolvedline getsredirect_yarn_classic_unresolved_entry_skippedand stays out of the in-run VEX. Before, it was silently counted as matched.vendor_yarn_classic_non_registry_entry_skipped, and refuses withvendor_lock_entry_not_rewritablewhen it is the only copy. A copy an older release already wired into.socket/vendor/stays a candidate, so an in-sync re-run is a byte-stable no-op, and is named withvendor_yarn_classic_non_registry_legacy_wiring(pointing tovendor --revert).docs/ecosystems.mdis updated.Duplicates deleted (before → after)
split("\n\n")yarn grammars: 7 → 1. The one left is the stanza view informats/yarn/stanzas.rs, used only by the berry writers, which re-order entries.with_body_field/repin_classic_block.rewrite_classic_block, hosted regex, restore regex): 3 → 1 (repin_classic_block).yarn_classic_block_head, vendoredclassify_classic_block, VEXclassic_block_purl): 3 → 1 (classic_key_real_name).__metadata/cacheKeyreaders (Fix yarn berry project gates drifting between modes (#628, #629) #657'smetadata_fields+scalar_field, this branch'sberry_cache_key,blocks::berry_metadata+berry_field): 3 → 1.ClassicBlockSource, inventoryis_git_resolution): 2 → 1 (CopySource).berry_sort_key,berry_entries_sorted,berry_reposition_blocks,yarn_classic_block_head,is_git_resolution. The vendor-module copies of every moved function are gone too.Testing
Rebased on
origin/main(431b8188, includes #657). All commands ran in the worktree throughheavy-job.shwithCARGO_INCREMENTAL=0:cargo test -p socket-patch-core --lib: 5648 passed (before the stanza test was added;formats::yarnre-run after it, 24 passed).cargo test -p socket-patch-cliwith--testin_process_redirect, in_process_vendor, in_process_rollback_hosted, hosted_memory_engine, hosted_memory_parity, in_process_get_modes, covgap_commands_vendor, covgap_commands_scan_hosted, covgap_commands_vex, covgap_commands_rollback, covgap_commands_scan_mod, e2e_vex_redirect, e2e_vex_vendor, e2e_safety_yarn_pnp, e2e_redirect_yarn_classic_build, e2e_vendor_yarn_classic_build, e2e_vendor_yarn_classic_dev_flow, cli_parse_list, global_scope_project_state, in_process_redirect_pnpm: all pass.mode_migration_npm: 18 pass, 1 fails (berry_vendored_then_hosted_takeover_leaves_pure_hosted), the same failure as on the pre-rebase branch and before this change: it expects a pristinepackage.jsonafter a hosted berry pin, but the pin has written rootresolutionsby design since Fix yarn berry hosted pin leaking npm auth (#404) #465. Not touched here.cargo clippy --workspace --all-features -- -D warnings -A unused-variables(the CI invocation;unused-variablesallowed only for the macOS-onlypython_crawler.rsunix_defaultwarning on main): clean.redirect_golden(the fixtures shared with depscan) pins a classic edit'soriginal/newas thesplit("\n\n")segment holding the block; the block-scan rewriter records that same segment again (ClassicSegments), soyarn_classic_rewrite.goldendiffers from main only by the unresolved-entry change.431b8188: yarn-classic hosted +4.6%, berry hosted +1.6%.yarn_classic_rewrite.goldenis re-blessed once more for the new unresolved-entry warning; every input digest is unchanged. (Earlier on this branch, a temporary oracle over all 400 seeds showed the new rewriter differing from the old only in the edit records' leading blank line and the unresolved-block integrity swap.)$in the artifact URL stays literal;file:/URL/codeload copies are skipped (hosted and vendored);file:/URL fork copies carry no registry verifiers; a header-less classic lock is read as classic by the fallback;scan_blocksagree on every line ending;CopySourcecases and the stanza round trip.Deferred
vendor_lockfile_version_unsupported. Whether vendor should wire it as classic is a policy call, so the current behavior is kept.npm:ranges. pnpm and bun copies belong to other workstreams (Fix 15 open pnpm issues across hosted, vendored and agent modes #1007, Fix open bun issues (#992, #861, #784, #764, #735, #635, #599, #578, #497, #443, #371) #1009).formats->vendoredges:formats/yarn/{source,blocks}.rsstill importvendor::npm_origin::npm_spec_is_registryandvendor::common::detect_eol, as otherformats/*modules do. Moving those into a shared npm-spec/text module is a later pass.🤖 Generated with Claude Code
Note
Medium Risk
Changes yarn classic pin/restore/rollback semantics for non-registry lock entries and refactors shared lockfile parsing used across hosted, vendor, and inventory paths; behavior is heavily tested but mistakes could mis-wire or fail rollbacks.
Overview
Consolidates all
yarn.lockparsing underformats/yarn(blocks,patterns,source,stanzas) so hosted rewriters, restorers, vendored backends, gates, and inventory share one block walk and field readers. Grammar detection is unified: whole-filesniff_grammar/grammar()replaces the old 30-line head sniff vs full-file split.Classic hosted and vendored paths stop using
split("\n\n")and regex field edits. They splice blocks in place viascan_blocks,repin_classic_block, andreplace_block, preserving mixed CRLF/LF outside edited spans and treating$in URLs literally. Berry writers useBerryStanzasfor BOM, EOL, and sorted re-insertion.B16 behavior change:
CopySourcedistinguishes registry copies from remote tarballs (file:tgz, URL forks, codeload shorthands). Non-registry blocks are no longer repointed to Socket’s registry artifact; they are skipped with new warnings, excluded from VEX where applicable, and rollback refuses legacy hosted pins on those keys. Lock inventory drops registry verifiers for fork copies. Docs indocs/ecosystems.mdare updated.Reviewed by Cursor Bugbot for commit 209e36e. Configure here.
Generated by Claude Code