@@ -173,16 +173,20 @@ pub(crate) struct LedgerSupplement {
173173/// the ledger `run` already loaded (`vendor::load_state`).
174174///
175175/// That holds only while the lock still wires the artifact. An entry the
176- /// lockfile in-use probe (the one the prune GC reverts by) answers
177- /// `Some(false)` for is the dependency having left the lock — bumped or
176+ /// project no longer consumes ([`Discovery::vendor_entry_in_use`] is
177+ /// `Some(false)` — the verdict the prune GC reverts by, read from `ctx`'s
178+ /// discovery) is the dependency having left the lock — bumped or
178179/// uninstalled — and is reported in [`LedgerSupplement::unwired`] instead:
179180/// re-vendoring it would fail against a lock that no longer has it. `None`
180- /// (no probe for the ecosystem, or no readable lock) keeps the entry.
181+ /// (no readable lock for the ecosystem) keeps the entry.
182+ ///
183+ /// [`Discovery::vendor_entry_in_use`]: socket_patch_core::vex::discover::Discovery::vendor_entry_in_use
181184pub ( crate ) async fn vendored_ledger_supplement (
182- common : & GlobalArgs ,
185+ ctx : & crate :: commands :: context :: ProjectContext < ' _ > ,
183186 crawled : & [ socket_patch_core:: crawlers:: types:: CrawledPackage ] ,
184187 state : & std:: io:: Result < VendorState > ,
185188) -> LedgerSupplement {
189+ let common = ctx. common ;
186190 let mut out = LedgerSupplement :: default ( ) ;
187191 if common. is_global ( ) {
188192 return out;
@@ -226,7 +230,12 @@ pub(crate) async fn vendored_ledger_supplement(
226230 continue ;
227231 }
228232 if let Some ( entry) = entry {
229- if crate :: commands:: vendor:: dispatch_in_use_one ( entry, & common. cwd ) . await == Some ( false )
233+ if ctx
234+ . discovery ( )
235+ . await
236+ . vendor_entry_in_use ( & common. cwd , entry)
237+ . await
238+ == Some ( false )
230239 {
231240 out. unwired . push ( ledger_key. clone ( ) ) ;
232241 continue ;
@@ -1120,9 +1129,13 @@ mod tests {
11201129 ..GlobalArgs :: default ( )
11211130 } ;
11221131 let state = socket_patch_core:: vendor:: load_state ( root) . await ;
1123- vendored_ledger_supplement ( & args, crawled, & state)
1124- . await
1125- . packages
1132+ vendored_ledger_supplement (
1133+ & crate :: commands:: context:: ProjectContext :: new ( & args) ,
1134+ crawled,
1135+ & state,
1136+ )
1137+ . await
1138+ . packages
11261139 }
11271140
11281141 /// A ledger entry vendored as `@3.0.2.0` is the crawled composer
@@ -1148,18 +1161,26 @@ mod tests {
11481161 cwd : tmp. path ( ) . to_path_buf ( ) ,
11491162 ..GlobalArgs :: default ( )
11501163 } ;
1151- let out = vendored_ledger_supplement ( & args, & [ crawled] , & Ok ( state. clone ( ) ) )
1152- . await
1153- . packages ;
1164+ let out = vendored_ledger_supplement (
1165+ & crate :: commands:: context:: ProjectContext :: new ( & args) ,
1166+ & [ crawled] ,
1167+ & Ok ( state. clone ( ) ) ,
1168+ )
1169+ . await
1170+ . packages ;
11541171 assert ! (
11551172 out. is_empty( ) ,
11561173 "{:?}" ,
11571174 out. iter( ) . map( |p| & p. purl) . collect:: <Vec <_>>( )
11581175 ) ;
11591176
1160- let out = vendored_ledger_supplement ( & args, & [ ] , & Ok ( state) )
1161- . await
1162- . packages ;
1177+ let out = vendored_ledger_supplement (
1178+ & crate :: commands:: context:: ProjectContext :: new ( & args) ,
1179+ & [ ] ,
1180+ & Ok ( state) ,
1181+ )
1182+ . await
1183+ . packages ;
11631184 assert_eq ! (
11641185 out. iter( ) . map( |p| p. purl. as_str( ) ) . collect:: <Vec <_>>( ) ,
11651186 vec![ "pkg:composer/psr/log@3.0.2.0" ]
@@ -1229,19 +1250,82 @@ mod tests {
12291250 } )
12301251 . to_string ( ) ;
12311252 let state = npm_ledger_with_lock ( tmp. path ( ) , Some ( & bumped) ) . await ;
1232- let out = vendored_ledger_supplement ( & args ( tmp. path ( ) ) , & [ ] , & state) . await ;
1253+ let out = vendored_ledger_supplement (
1254+ & crate :: commands:: context:: ProjectContext :: new ( & args ( tmp. path ( ) ) ) ,
1255+ & [ ] ,
1256+ & state,
1257+ )
1258+ . await ;
12331259 assert ! ( out. packages. is_empty( ) , "{:?}" , out. packages) ;
12341260 assert_eq ! ( out. unwired, vec![ "pkg:npm/left-pad@1.3.0" . to_string( ) ] ) ;
12351261
12361262 // Uninstalled: the lock has no left-pad at all.
12371263 let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
12381264 let removed = r#"{"name":"app","lockfileVersion":3,"packages":{"":{"name":"app"}}}"# ;
12391265 let state = npm_ledger_with_lock ( tmp. path ( ) , Some ( removed) ) . await ;
1240- let out = vendored_ledger_supplement ( & args ( tmp. path ( ) ) , & [ ] , & state) . await ;
1266+ let out = vendored_ledger_supplement (
1267+ & crate :: commands:: context:: ProjectContext :: new ( & args ( tmp. path ( ) ) ) ,
1268+ & [ ] ,
1269+ & state,
1270+ )
1271+ . await ;
12411272 assert ! ( out. packages. is_empty( ) , "{:?}" , out. packages) ;
12421273 assert_eq ! ( out. unwired, vec![ "pkg:npm/left-pad@1.3.0" . to_string( ) ] ) ;
12431274 }
12441275
1276+ /// B19: the supplement and the prune GC share one in-use verdict for
1277+ /// every ecosystem, not only npm/cargo/pypi-requirements. A COMPOSER
1278+ /// entry whose dependency composer.lock bumped to a registry release is
1279+ /// unwired — before, it was resurrected as a discovered package forever.
1280+ #[ tokio:: test]
1281+ async fn ledger_supplement_reports_a_bumped_composer_entry_unwired ( ) {
1282+ const COMPOSER_PURL : & str = "pkg:composer/monolog/monolog@3.0.0" ;
1283+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
1284+ let root = tmp. path ( ) ;
1285+ let leaf = format ! ( ".socket/vendor/composer/{VENDORED_UUID}/monolog/monolog@3.0.0" ) ;
1286+ let entry: socket_patch_core:: vendor:: VendorEntry =
1287+ serde_json:: from_value ( serde_json:: json!( {
1288+ "ecosystem" : "composer" ,
1289+ "basePurl" : COMPOSER_PURL ,
1290+ "uuid" : VENDORED_UUID ,
1291+ "artifact" : { "path" : leaf, "sha256" : "" } ,
1292+ "wiring" : [ ] ,
1293+ "detached" : true ,
1294+ } ) )
1295+ . unwrap ( ) ;
1296+ let mut state = VendorState :: default ( ) ;
1297+ state. entries . insert ( COMPOSER_PURL . to_string ( ) , entry) ;
1298+ std:: fs:: write (
1299+ root. join ( "composer.lock" ) ,
1300+ serde_json:: json!( {
1301+ "packages" : [ {
1302+ "name" : "monolog/monolog" ,
1303+ "version" : "3.1.0" ,
1304+ "dist" : {
1305+ "type" : "zip" ,
1306+ "url" : "https://api.github.com/repos/Seldaek/monolog/zipball/abc" ,
1307+ "reference" : "abc" ,
1308+ } ,
1309+ } ] ,
1310+ "packages-dev" : [ ] ,
1311+ } )
1312+ . to_string ( ) ,
1313+ )
1314+ . unwrap ( ) ;
1315+ let args = GlobalArgs {
1316+ cwd : root. to_path_buf ( ) ,
1317+ ..GlobalArgs :: default ( )
1318+ } ;
1319+ let out = vendored_ledger_supplement (
1320+ & crate :: commands:: context:: ProjectContext :: new ( & args) ,
1321+ & [ ] ,
1322+ & Ok ( state) ,
1323+ )
1324+ . await ;
1325+ assert ! ( out. packages. is_empty( ) , "{:?}" , out. packages) ;
1326+ assert_eq ! ( out. unwired, vec![ COMPOSER_PURL . to_string( ) ] ) ;
1327+ }
1328+
12451329 /// The fresh-clone case the supplement exists for: the lock still
12461330 /// resolves through the committed artifact, so the entry stays
12471331 /// discoverable. With no lock at all, nothing proves the entry unused,
@@ -1260,7 +1344,12 @@ mod tests {
12601344 ..GlobalArgs :: default ( )
12611345 } ;
12621346 let state = npm_ledger_with_lock ( tmp. path ( ) , lock. as_deref ( ) ) . await ;
1263- let out = vendored_ledger_supplement ( & args, & [ ] , & state) . await ;
1347+ let out = vendored_ledger_supplement (
1348+ & crate :: commands:: context:: ProjectContext :: new ( & args) ,
1349+ & [ ] ,
1350+ & state,
1351+ )
1352+ . await ;
12641353 assert_eq ! (
12651354 out. packages
12661355 . iter( )
0 commit comments