Repository navigation
fix(fix-path): nested and bundled npm parents, copy-exact attribution, direct-upgrade commands (audit A7/E8) - #962
Merged
Conversation
…, direct-upgrade commands (audit A7/E8) A7: package-lock edges were read from first-level keys only, so a copy held by a nested parent (navcal's brace-expansion 2.0.2 under glob/node_modules/minimatch 9.0.5) read "no parent 4DA can read". Nested keys are parents now; first-level and root edges are unchanged. E8, transitive: - Bundled copies (inBundle) hang off their bundle owner as an exact pin marked "(bundled)": no refresh and no npm override reaches them. override_hint takes the bundled flag, so the Upgrade Plan and the Brief's fallback route both inherit it. The plan names the owner and never prints the marker. - A parent is attributed only to the copy the package-lock v2/v3 tree resolves for it (npm's lookup). NodeGoat's needle 2.2.4 (^2.1.2) resolves the hoisted debug 2.6.9 and is no longer told `npm update debug` for 2.2.0. E8, direct: Manager::direct_command (npm install / pnpm up / yarn upgrade / yarn up / cargo add [--dev]), each checked live against its tool, with rule vectors fc-028..fc-037. The TS engine skips them for now. The surfaces call it after #961. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
…-068)
A bundled requirement (`bundled: true`) is the owner's exact pin: the parent
must move. The optional `override` field says whether an override may be
offered: never for a bundled copy, which npm `overrides` do not reach. That
was checked with npm 10.9.2 on 2026-10-15: nyc 7.1.0's bundled debug stayed
2.2.0 under `"overrides": {"debug": "2.6.9"}` and under `npm update debug`.
rv-068 is the contrast: an ordinary exact pin does take an override.
The `ts` entries in engines_skip.json are dropped. @4da/mcp-server keeps its
own conformance/vectors-skip.json and implements these vectors with the same
pin.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
… bundlers only, copy-scoped Brief override - An owner declares what it bundles (npm 3.10.10 declares all 82 of its bundled packages), and its range was stored as a second, refreshable edge to the bundled copy. The owner's declared children that it bundles are now skipped, so the bundled copy keeps exactly one edge: the owner's pin. - Bundled means bundled inside a DEPENDENCY. A project's or workspace member's own bundleDependencies also set inBundle (Arborist's bundler can be the root), but those copies are in the user's tree and keep their ordinary edges (fix_path::npm_lock_entry_bundled / npm_bundle_owner, used by the scanner and by the lockfile-tree attribution). - npm_lock_resolutions skips bundled copies of the parent, and answers None when no copy of the parent declares the child, so the range attribution still applies. - npm_lock_tree finds the lockfile on the same bounded ascent as Manager::detect, so a workspace member's root lockfile is read. It caches the parsed tree per file, by mtime and size. - The Brief's fallback route suppresses the override only for the copy it describes; another copy's bundle says nothing about this one. - Manager::direct_command and the fc-028..fc-037 vectors move to the C2 change, which wires them; production does not call them yet. - Tests: realistic bundle owners that declare what they bundle; root and workspace bundles; the resolver with a scoped parent, a workspace member and a bundled twin; an end-to-end plan over a real package-lock.json on disk. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
runyourempire
force-pushed
the
fix/fx-a7-e8-nested-parents-direct-cmds
branch
from
October 16, 2026 00:17
5decd5b to
184a3f9
Compare
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.
What
Fixes two findings from the 2026-10-12 adversarial audit:
node_modulesparents were reported as "no parent 4DA can read".1. A7: nested parents (
ace/scanner.rs)parse_package_lock_edgesskipped every nestedpackageskey ("first-level edges only"). Sonode_modules/glob/node_modules/minimatch(minimatch 9.0.5,^2.0.1) was never stored. The live DB held onlyminimatch 3.1.2 -> brace-expansion ^1.1.7for navcal.Nested keys now produce edges, named after the last
node_modules/segment (scope kept). First-level and root edges are unchanged; the existing tests pass unmodified.2. E8: bundled copies (
ace/scanner.rs,osv/fix_path.rs,osv/parent_hint.rs,evidence/upgrade_parent.rs,brief_facts_copies.rs)A copy with
inBundle: trueships inside its owner's tarball:npm updatecannot move it;overridesdo not reach it;It now gets ONE edge, from the bundle owner (the nearest enclosing entry that is not itself
inBundle, skipping levels the lockfile does not list), with spec2.2.0 (bundled). Bundled packages emit no edges of their own, because babel-traverse's^2.2.0inside nyc would read as "a refresh is enough".fix_path::override_hint(manager, eco, bundled)returns no override for a bundled copy. The source decides it, so every caller inherits the fix:parent_hint::annotate, and the Brief's fallback route inbrief_facts_copies.rs.3. E8: which copy a parent holds (
osv/fix_path.rs,osv/parent_hint.rs)A parent was attributed to every copy its range admits. NodeGoat's needle 2.2.4 (
^2.1.2) was named a parent of debug 2.2.0 and toldnpm update debug, but it resolves the hoisted 2.6.9.fix_path::npm_lock_resolutionsnow reads the package-lock v2/v3 tree with npm's own lookup: the parent's folder, then each enclosing folder, then the root.parent_hintuses it through a newReaders::resolved_children; one parse per project lockfile is cached for the call. A v1 lockfile, or any other tool, keeps the range-based attribution.4. The bundled rule is a shared vector (rv-066..rv-068)
A requirement can be
bundled: true; a line can carryoverride(whether an override may be offered). A bundled copy needs its owner to move, and no override is offered; an ordinary exact pin (rv-068) also needs its parent to move, but does take an override. Both premises were checked live with npm 10.9.2 (2026-10-15): nyc 7.1.0's bundled debug stayed at 2.2.0 under"overrides": {"debug": "2.6.9"}and undernpm update debug.@4da/mcp-serverimplements the same vectors (its PR follows this merge, pinned to the merged commit).5. Review fixes (second round)
fix_path::npm_lock_entry_bundled,npm_bundle_owner). A project's or workspace member's ownbundleDependenciesalso setinBundle, but those copies are in the user's tree: ordinary edges, refresh and overrides as usual.npm_lock_resolutionsskips bundled copies of the parent, and returnsNonewhen no copy of the parent declares the child, so the range fallback applies.npm_lock_treeuses the same bounded ascent asManager::detect, so a workspace member's root lockfile is read. It parses once per file (mtime + size, at most 16 cached).Manager::direct_commandand its vectors moved to C2, so nothing ships that production does not call.Before / after, real lockfiles
Each line is the link
parent_hintbuilds. "Before" is origin/main's first-level-only parser; "after" is this PR with the lockfile tree.^2.0.1admits 2.1.7: refresh, read from the requirementeditors/vscode, brace-expansion 5.0.6 → 5.0.12^5.0.5: refresh, readnpm update debug(false)npm update debug"~2.2.0), mocha (pin), nyc and npm (bundled, no override); needle, snapdragon, tap-mocha-reporter and undefsafe resolve the hoisted 2.6.9 and are no longer attributednpm update debugmoveEvery reader of the edge spec, and the marker
X (bundled)db/dependencies/queries.rs(store/read)osv/parent_hint.rsread_edges/parent_links_with/resolutions/direct_throughrequirement_admitsandstrip_specdrop the text after(, so it reads as an exact pin;is_bundledchecks it explicitlyevidence/upgrade_parent.rs(link_sentence,requirement_note)brief_facts_build.rsdirect_parent(Brief)Requirement::npm/requirement_admitsgive an exact pin;requirementsfeedrefresh_verdictonly and are never printedbrief_facts_copies.rsoverride_hint(.., bundled)from the plan's links for the SAME copy (installed)ace/dep_scope.rs@4da/mcp-serverdependency_edges(grep ofsrc/: none)crates/fourda-depengine(#960, merged)osv/vectors_tests.rsbundled: truevector requirement is built astext + BUNDLED_SUFFIX, exactly as the scanner stores itTests
Each new test failed first on origin/main.
package_lock_nested_packages_are_parentspackage_lock_bundled_copies_hang_off_their_owner, with realistic owners that declare what they bundle, including npm → semverpackage_lock_root_bundles_keep_their_edgesnpm_lock_resolutions_follow_npms_lookup: scoped parent, workspace member, bundled twin,Nonenpm_lock_entry_bundled_needs_a_dependency_as_bundlera_nested_parent_is_named_for_its_own_copya_bundled_copy_waits_on_its_ownerthe_lockfile_tree_decides_which_copy_a_parent_holdsa_bundled_copy_names_its_owner_and_no_overridethe_plan_reads_the_lockfile_tree_on_disk: a real project dir with package.json + package-lock.json, through Manager::Npm detection, the cached tree and the plan text. It ran (not skipped).a_bundled_copy_gets_no_override_on_the_brief_route, including another copy's bundleRebased on
d7db30d27(#961). 104 targeted tests pass;cargo clippy --locked -- -D warningsand rustfmt are clean; the file-size gate gives warnings only (parent_hint.rs782,fix_path.rs755 lines; the error threshold is 1000).Follow-ups (not here)
Manager::direct_command(checked live per tool), its vectors fc-028..fc-037, and the wiring into the direct rows. The reviewer's notes are in scope:--builddeps; workspace-inherited, target-specific and renamed Cargo deps;--save-exactfor exact pins.direct_parentstill attributes by range;npm_lock_resolutionsis ready for it.🤖 Generated with Claude Code
https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH