1use std::collections::HashSet;
16use std::path::{Path, PathBuf};
17
18use super::super::alc_toml::{self, PackageDep};
19use super::super::lockfile::load_lockfile;
20use super::super::manifest::{load_manifest, ManifestEntry};
21use super::super::resolve::packages_dir;
22use super::super::source::PackageSource;
23use super::super::AppService;
24use super::install::InstallSource;
25
26enum RepairOutcome {
28 Repaired { source: String },
30 Skipped,
32 Unrepairable {
37 kind: &'static str,
38 reason: String,
39 suggestion: String,
40 },
41 Failed { reason: String },
43}
44
45#[derive(Default)]
47struct Buckets {
48 repaired: Vec<serde_json::Value>,
49 skipped: Vec<serde_json::Value>,
50 unrepairable: Vec<serde_json::Value>,
51 failed: Vec<serde_json::Value>,
52}
53
54impl Buckets {
55 fn any_matched(&self) -> bool {
56 !self.repaired.is_empty()
57 || !self.skipped.is_empty()
58 || !self.unrepairable.is_empty()
59 || !self.failed.is_empty()
60 }
61
62 fn into_json(self) -> String {
63 serde_json::json!({
64 "repaired": self.repaired,
65 "skipped": self.skipped,
66 "unrepairable": self.unrepairable,
67 "failed": self.failed,
68 })
69 .to_string()
70 }
71}
72
73pub(super) fn symlink_dangling_suggestion(name: &str) -> String {
76 format!("alc_pkg_unlink({name:?}) then alc_pkg_link with the new path")
77}
78
79#[derive(Debug, PartialEq, Eq)]
83pub(super) enum AliveBucket {
84 Unregistered,
87}
88
89fn push_installed_outcome(name: &str, outcome: RepairOutcome, buckets: &mut Buckets) {
94 match outcome {
95 RepairOutcome::Repaired { source } => buckets.repaired.push(serde_json::json!({
96 "name": name,
97 "kind": "installed_missing",
98 "action": "reinstall",
99 "source": source,
100 })),
101 RepairOutcome::Skipped => buckets.skipped.push(serde_json::json!({
102 "name": name,
103 "reason": "healthy",
104 })),
105 RepairOutcome::Unrepairable {
106 kind,
107 reason,
108 suggestion,
109 } => buckets.unrepairable.push(serde_json::json!({
110 "name": name,
111 "kind": kind,
112 "reason": reason,
113 "suggestion": suggestion,
114 })),
115 RepairOutcome::Failed { reason } => buckets.failed.push(serde_json::json!({
116 "name": name,
117 "kind": "installed_missing",
118 "reason": reason,
119 })),
120 }
121}
122
123impl AppService {
124 pub async fn pkg_repair(
134 &self,
135 name: Option<String>,
136 project_root: Option<String>,
137 ) -> Result<String, String> {
138 let app_dir = self.log_config.app_dir();
139 let manifest = load_manifest(&app_dir)?;
140 let pkg_dir = packages_dir(&app_dir);
141 let resolved_root = self.resolve_root(project_root.as_deref());
142
143 let mut buckets = Buckets::default();
144 let target_filter = name.as_deref();
145
146 for (pkg_name, entry) in &manifest.packages {
148 if let Some(target) = target_filter {
149 if target != pkg_name.as_str() {
150 continue;
151 }
152 }
153 let outcome = self.repair_installed(pkg_name, entry, &pkg_dir).await;
154 push_installed_outcome(pkg_name, outcome, &mut buckets);
155 }
156
157 collect_unattached_dangling_symlinks(
159 &pkg_dir,
160 target_filter,
161 &manifest.packages,
162 &mut buckets.unrepairable,
163 );
164
165 if let Some(root) = resolved_root.as_ref() {
168 collect_path_missing(
169 root,
170 target_filter,
171 "project",
172 &mut buckets.unrepairable,
173 ProjectPathSource::Toml,
174 );
175 collect_path_missing(
176 root,
177 target_filter,
178 "variant",
179 &mut buckets.unrepairable,
180 ProjectPathSource::Local,
181 );
182 }
183
184 if let Some(target) = target_filter {
185 if !buckets.any_matched() {
186 return Err(format!(
187 "Package '{target}' not found in installed.json, ~/.algocline/packages/, alc.toml, or alc.local.toml"
188 ));
189 }
190 }
191
192 Ok(buckets.into_json())
193 }
194
195 async fn repair_installed(
201 &self,
202 name: &str,
203 entry: &ManifestEntry,
204 pkg_dir: &Path,
205 ) -> RepairOutcome {
206 let dest = pkg_dir.join(name);
207
208 let is_symlink = dest
209 .symlink_metadata()
210 .map(|m| m.file_type().is_symlink())
211 .unwrap_or(false);
212 if is_symlink {
213 let target_alive = dest.try_exists().unwrap_or(false);
215 if target_alive {
216 return RepairOutcome::Skipped;
217 }
218 let link_target = dest
219 .read_link()
220 .map(|t| t.display().to_string())
221 .unwrap_or_else(|_| "<unknown>".to_string());
222 return RepairOutcome::Unrepairable {
223 kind: "symlink_dangling",
224 reason: format!("symlink target missing: {link_target}"),
225 suggestion: symlink_dangling_suggestion(name),
226 };
227 }
228
229 if dest.exists() {
230 return RepairOutcome::Skipped;
231 }
232
233 let install_source = match &entry.source {
243 PackageSource::Path { path } => InstallSource::LocalPath(PathBuf::from(path)),
244 PackageSource::Git { url, .. } => InstallSource::GitUrl(normalize_git_url(url)),
245 PackageSource::Bundled { .. } => {
246 return RepairOutcome::Unrepairable {
247 kind: "installed_missing",
248 reason: "bundled package — restore via `alc_init` or reinstall algocline"
249 .to_string(),
250 suggestion: "alc_init (reinstalls bundled packages from the algocline binary)"
251 .to_string(),
252 };
253 }
254 PackageSource::Installed => {
255 return RepairOutcome::Unrepairable {
260 kind: "installed_missing",
261 reason: "legacy 'installed' marker carries no source path".to_string(),
262 suggestion: "alc_pkg_install <path-or-url> to re-record source, \
263 then alc_pkg_repair"
264 .to_string(),
265 };
266 }
267 PackageSource::Unknown => {
268 return RepairOutcome::Unrepairable {
272 kind: "installed_missing",
273 reason: "source unknown (legacy entry; run alc_hub_reindex)".to_string(),
274 suggestion: "alc_hub_reindex to rebuild the index, or \
275 alc_pkg_install <path-or-url> to re-record source"
276 .to_string(),
277 };
278 }
279 };
280
281 if let InstallSource::LocalPath(ref p) = install_source {
294 if !p.exists() {
295 return RepairOutcome::Unrepairable {
296 kind: "installed_missing",
297 reason: format!("source directory missing: {}", p.display()),
298 suggestion: format!(
299 "alc_pkg_install from a valid source, or remove the '{name}' entry from ~/.algocline/installed.json"
300 ),
301 };
302 }
303 if !p.join(name).join("init.lua").exists() {
304 return RepairOutcome::Unrepairable {
305 kind: "installed_missing",
306 reason: format!(
307 "source directory has no init.lua at root: {}",
308 p.display()
309 ),
310 suggestion: format!(
311 "alc_pkg_install from a valid source, or remove the '{name}' entry from ~/.algocline/installed.json"
312 ),
313 };
314 }
315 }
316
317 match self.pkg_install_typed(install_source, None, None).await {
323 Ok(_) => RepairOutcome::Repaired {
324 source: entry.source.display_string(),
328 },
329 Err(e) => RepairOutcome::Failed { reason: e },
330 }
331 }
332}
333
334fn normalize_git_url(url: &str) -> String {
342 super::install::prefix_git_scheme_if_missing(url)
343}
344
345pub(super) fn collect_unattached_dangling_symlinks(
349 pkg_dir: &Path,
350 target_filter: Option<&str>,
351 manifest_names: &std::collections::BTreeMap<String, ManifestEntry>,
352 unrepairable: &mut Vec<serde_json::Value>,
353) {
354 let read = match std::fs::read_dir(pkg_dir) {
355 Ok(r) => r,
356 Err(e) => {
357 tracing::warn!(
358 "pkg: failed to read packages_dir at {}: {e}",
359 pkg_dir.display()
360 );
361 return;
362 }
363 };
364
365 for dir_entry_result in read {
366 let dir_entry = match dir_entry_result {
367 Ok(e) => e,
368 Err(e) => {
369 tracing::warn!(
375 "pkg: skipping unreadable entry in {}: {e}",
376 pkg_dir.display()
377 );
378 continue;
379 }
380 };
381 let path = dir_entry.path();
382 let pkg_name = dir_entry.file_name().to_string_lossy().to_string();
383
384 if let Some(target) = target_filter {
385 if target != pkg_name.as_str() {
386 continue;
387 }
388 }
389 if manifest_names.contains_key(&pkg_name) {
390 continue;
391 }
392
393 let is_symlink = path
394 .symlink_metadata()
395 .map(|m| m.file_type().is_symlink())
396 .unwrap_or(false);
397 if !is_symlink {
398 continue;
399 }
400 let target_exists = path.try_exists().unwrap_or(false);
401 if target_exists {
402 continue;
403 }
404
405 let link_target = path
406 .read_link()
407 .map(|t| t.display().to_string())
408 .unwrap_or_else(|_| "<unknown>".to_string());
409
410 unrepairable.push(serde_json::json!({
411 "name": pkg_name,
412 "kind": "symlink_dangling",
413 "reason": format!("symlink target missing: {link_target}"),
414 "suggestion": symlink_dangling_suggestion(&pkg_name),
415 }));
416 }
417}
418
419#[derive(Debug, Clone, Copy)]
421pub(super) enum ProjectPathSource {
422 Toml,
424 Local,
426}
427
428pub(super) fn collect_path_missing(
432 root: &Path,
433 target_filter: Option<&str>,
434 scope: &'static str,
435 unrepairable: &mut Vec<serde_json::Value>,
436 src: ProjectPathSource,
437) {
438 let loaded = match src {
439 ProjectPathSource::Toml => alc_toml::load_alc_toml(root),
440 ProjectPathSource::Local => alc_toml::load_alc_local_toml(root),
441 };
442 let Ok(Some(toml_data)) = loaded else {
443 return;
444 };
445
446 let lock_lookup = if matches!(src, ProjectPathSource::Toml) {
456 load_lockfile(root).ok().flatten().map(|l| {
457 l.packages
458 .into_iter()
459 .filter_map(|p| match p.source {
460 PackageSource::Path { path } => Some((p.name, path)),
461 _ => None,
462 })
463 .collect::<std::collections::HashMap<String, String>>()
464 })
465 } else {
466 None
467 };
468
469 for (name, dep) in &toml_data.packages {
470 if let Some(t) = target_filter {
471 if t != name.as_str() {
472 continue;
473 }
474 }
475
476 let raw = match dep {
477 PackageDep::Path { path, .. } => path,
478 _ => continue,
479 };
480
481 let resolved_raw = lock_lookup
482 .as_ref()
483 .and_then(|m| m.get(name).cloned())
484 .unwrap_or_else(|| raw.clone());
485
486 let p = Path::new(&resolved_raw);
487 let abs = if p.is_absolute() {
488 p.to_path_buf()
489 } else {
490 root.join(p)
491 };
492
493 if abs.exists() {
494 continue;
495 }
496
497 let suggestion = match src {
498 ProjectPathSource::Toml => {
499 format!("update or remove [packages.{name}] in alc.toml")
500 }
501 ProjectPathSource::Local => {
502 format!("alc_pkg_unlink({name:?}) or update [packages.{name}] in alc.local.toml")
503 }
504 };
505
506 unrepairable.push(serde_json::json!({
507 "name": name,
508 "kind": "path_missing",
509 "scope": scope,
510 "reason": format!("declared path does not exist: {}", abs.display()),
511 "suggestion": suggestion,
512 }));
513 }
514}
515
516pub(super) fn collect_unregistered_pkg_dirs(
542 pkg_dir: &Path,
543 registered: &HashSet<String>,
544 registered_paths: &[PathBuf],
545 target_filter: Option<&str>,
546) -> Result<Vec<serde_json::Value>, String> {
547 let read = match std::fs::read_dir(pkg_dir) {
548 Ok(r) => r,
549 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
550 return Ok(vec![]);
552 }
553 Err(e) => {
554 return Err(format!(
555 "pkg: failed to read packages_dir at {}: {e}",
556 pkg_dir.display()
557 ));
558 }
559 };
560
561 let mut entries = Vec::new();
562
563 for dir_entry_result in read {
564 let dir_entry = match dir_entry_result {
565 Ok(e) => e,
566 Err(e) => {
567 tracing::warn!(
568 "pkg: skipping unreadable entry in {}: {e}",
569 pkg_dir.display()
570 );
571 continue;
572 }
573 };
574
575 let path = dir_entry.path();
576 let pkg_name = dir_entry.file_name().to_string_lossy().to_string();
577
578 if let Some(target) = target_filter {
580 if target != pkg_name.as_str() {
581 continue;
582 }
583 }
584
585 if registered.contains(&pkg_name) {
587 continue;
588 }
589
590 let meta = match path.symlink_metadata() {
592 Ok(m) => m,
593 Err(e) => {
594 tracing::warn!("pkg: cannot stat {}: {e}", path.display());
595 continue;
596 }
597 };
598 if !meta.is_dir() {
599 continue;
601 }
602 if !path.join("init.lua").exists() {
603 continue;
605 }
606
607 let canonical_pkg_path = match path.canonicalize() {
610 Ok(c) => c,
611 Err(e) => {
612 return Err(format!(
613 "pkg: failed to canonicalize existing dir {}: {e}",
614 path.display()
615 ));
616 }
617 };
618 if registered_paths.contains(&canonical_pkg_path) {
619 continue;
620 }
621
622 let abs_path = path.display().to_string();
625 let suggestion = serde_json::json!([
626 format!(
627 "If this pkg was scaffolded outside `alc_pkg_scaffold` and you want it installed: \
628 `alc_pkg_install --force {abs_path}` (re-copy + register in installed.json)"
629 ),
630 format!(
631 "If you are actively iterating on this pkg in-tree: \
632 `alc_pkg_link {abs_path}` (symlink-based, no copy)"
633 ),
634 format!("If this dir is stale/abandoned: `rm -rf {abs_path}` to clean it up"),
635 "Note: source is unknown — git URL cannot be inferred from the bare directory. \
636 Re-record via one of the above."
637 .to_string(),
638 ]);
639
640 entries.push(serde_json::json!({
641 "name": pkg_name,
642 "kind": "unregistered_pkg",
643 "source": "unknown",
644 "reason": format!(
645 "physical dir with init.lua exists but is not registered in \
646 installed.json, alc.toml, or alc.local.toml: {}",
647 path.display()
648 ),
649 "suggestion": suggestion,
650 }));
651 }
652
653 Ok(entries)
654}
655
656impl AppService {
657 pub(super) async fn collect_alive_unregistered_symlinks(
694 &self,
695 pkg_dir: &Path,
696 registered: &HashSet<String>,
697 registered_paths: &[PathBuf],
698 target_filter: Option<&str>,
699 ) -> Result<Vec<(String, AliveBucket)>, String> {
700 let read = match std::fs::read_dir(pkg_dir) {
701 Ok(r) => r,
702 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
703 return Ok(vec![]);
705 }
706 Err(e) => {
707 return Err(format!(
708 "pkg: failed to read packages_dir at {}: {e}",
709 pkg_dir.display()
710 ));
711 }
712 };
713
714 let mut entries = Vec::new();
715
716 for dir_entry_result in read {
717 let dir_entry = match dir_entry_result {
718 Ok(e) => e,
719 Err(e) => {
720 tracing::warn!(
721 "pkg: skipping unreadable entry in {}: {e}",
722 pkg_dir.display()
723 );
724 continue;
725 }
726 };
727
728 let path = dir_entry.path();
729 let pkg_name = dir_entry.file_name().to_string_lossy().to_string();
730
731 if let Some(target) = target_filter {
733 if target != pkg_name.as_str() {
734 continue;
735 }
736 }
737
738 if registered.contains(&pkg_name) {
740 continue;
741 }
742
743 let meta = match path.symlink_metadata() {
745 Ok(m) => m,
746 Err(e) => {
747 tracing::warn!("pkg: cannot stat {}: {e}", path.display());
748 continue;
749 }
750 };
751 if !meta.file_type().is_symlink() {
752 continue;
754 }
755
756 let target_exists = path.try_exists().unwrap_or(false);
759 if !target_exists {
760 continue;
761 }
762
763 if !path.join("init.lua").exists() {
765 continue;
766 }
767
768 let canonical_pkg_path = match path.canonicalize() {
772 Ok(c) => c,
773 Err(e) => {
774 return Err(format!(
775 "pkg: failed to canonicalize symlink target {}: {e}",
776 path.display()
777 ));
778 }
779 };
780 if registered_paths.contains(&canonical_pkg_path) {
781 continue;
782 }
783
784 entries.push((pkg_name, AliveBucket::Unregistered));
788 }
789
790 Ok(entries)
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797
798 #[cfg(unix)]
799 mod alive_symlink_tests {
800 use super::super::super::super::test_support::make_app_service_at;
801 use super::*;
802 use std::os::unix::fs::symlink as unix_symlink;
803
804 fn write_auto_library_init_lua(pkg_dir: &std::path::Path, pkg_name: &str) {
807 let pkg = pkg_dir.join(pkg_name);
808 std::fs::create_dir_all(&pkg).expect("create pkg dir");
809 std::fs::write(
810 pkg.join("init.lua"),
811 format!("local M = {{}}\nM.meta = {{ name = \"{pkg_name}\" }}\nreturn M\n"),
812 )
813 .expect("write init.lua");
814 }
815
816 fn write_explicit_type_init_lua(pkg_dir: &std::path::Path, pkg_name: &str) {
819 let pkg = pkg_dir.join(pkg_name);
820 std::fs::create_dir_all(&pkg).expect("create pkg dir");
821 std::fs::write(
822 pkg.join("init.lua"),
823 format!(
824 "local M = {{}}\nM.meta = {{ name = \"{pkg_name}\", type = \"library\" }}\nreturn M\n"
825 ),
826 )
827 .expect("write init.lua");
828 }
829
830 #[tokio::test]
832 async fn dangling_symlink_excluded() {
833 let tmp = tempfile::tempdir().expect("create tempdir");
834 let pkg_dir = tmp.path().join("packages");
835 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
836
837 let link = pkg_dir.join("ghost_pkg");
839 unix_symlink(tmp.path().join("does_not_exist"), &link)
840 .expect("create dangling symlink");
841
842 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
843 let registered = HashSet::new();
844 let registered_paths: Vec<PathBuf> = vec![];
845 let result = svc
846 .collect_alive_unregistered_symlinks(&pkg_dir, ®istered, ®istered_paths, None)
847 .await
848 .expect("helper should not error");
849
850 assert!(
851 result.is_empty(),
852 "dangling symlink must not appear in result"
853 );
854 }
855
856 #[tokio::test]
860 async fn alive_unregistered_is_always_unregistered() {
861 let tmp = tempfile::tempdir().expect("create tempdir");
862 let real_pkgs = tmp.path().join("real");
863 let pkg_dir = tmp.path().join("packages");
864 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
865
866 write_auto_library_init_lua(&real_pkgs, "my_lib");
868
869 unix_symlink(real_pkgs.join("my_lib"), pkg_dir.join("my_lib"))
871 .expect("create alive symlink");
872
873 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
874 let registered = HashSet::new();
875 let registered_paths: Vec<PathBuf> = vec![];
876 let result = svc
877 .collect_alive_unregistered_symlinks(&pkg_dir, ®istered, ®istered_paths, None)
878 .await
879 .expect("helper should not error");
880
881 assert_eq!(result.len(), 1);
882 assert_eq!(result[0].0, "my_lib");
883 assert_eq!(result[0].1, AliveBucket::Unregistered);
884 }
885
886 #[tokio::test]
888 async fn alive_unregistered_explicit_type_routes_to_unregistered() {
889 let tmp = tempfile::tempdir().expect("create tempdir");
890 let real_pkgs = tmp.path().join("real");
891 let pkg_dir = tmp.path().join("packages");
892 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
893
894 write_explicit_type_init_lua(&real_pkgs, "explicit_lib");
895
896 unix_symlink(real_pkgs.join("explicit_lib"), pkg_dir.join("explicit_lib"))
897 .expect("create alive symlink");
898
899 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
900 let registered = HashSet::new();
901 let registered_paths: Vec<PathBuf> = vec![];
902 let result = svc
903 .collect_alive_unregistered_symlinks(&pkg_dir, ®istered, ®istered_paths, None)
904 .await
905 .expect("helper should not error");
906
907 assert_eq!(result.len(), 1);
908 assert_eq!(result[0].0, "explicit_lib");
909 assert_eq!(result[0].1, AliveBucket::Unregistered);
910 }
911
912 #[tokio::test]
914 async fn alive_registered_pkg_excluded() {
915 let tmp = tempfile::tempdir().expect("create tempdir");
916 let real_pkgs = tmp.path().join("real");
917 let pkg_dir = tmp.path().join("packages");
918 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
919
920 write_auto_library_init_lua(&real_pkgs, "known_pkg");
921
922 unix_symlink(real_pkgs.join("known_pkg"), pkg_dir.join("known_pkg"))
923 .expect("create alive symlink");
924
925 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
926 let mut registered = HashSet::new();
927 registered.insert("known_pkg".to_string());
928 let registered_paths: Vec<PathBuf> = vec![];
929 let result = svc
930 .collect_alive_unregistered_symlinks(&pkg_dir, ®istered, ®istered_paths, None)
931 .await
932 .expect("helper should not error");
933
934 assert!(
935 result.is_empty(),
936 "registered pkg must not appear in result"
937 );
938 }
939
940 #[tokio::test]
942 async fn target_filter_restricts_output() {
943 let tmp = tempfile::tempdir().expect("create tempdir");
944 let real_pkgs = tmp.path().join("real");
945 let pkg_dir = tmp.path().join("packages");
946 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
947
948 write_auto_library_init_lua(&real_pkgs, "lib_a");
949 write_auto_library_init_lua(&real_pkgs, "lib_b");
950
951 unix_symlink(real_pkgs.join("lib_a"), pkg_dir.join("lib_a"))
952 .expect("create symlink lib_a");
953 unix_symlink(real_pkgs.join("lib_b"), pkg_dir.join("lib_b"))
954 .expect("create symlink lib_b");
955
956 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
957 let registered = HashSet::new();
958 let registered_paths: Vec<PathBuf> = vec![];
959 let result = svc
960 .collect_alive_unregistered_symlinks(
961 &pkg_dir,
962 ®istered,
963 ®istered_paths,
964 Some("lib_a"),
965 )
966 .await
967 .expect("helper should not error");
968
969 assert_eq!(result.len(), 1);
970 assert_eq!(result[0].0, "lib_a");
971 }
972
973 #[tokio::test]
976 async fn registered_path_dep_excluded() {
977 let tmp = tempfile::tempdir().expect("create tempdir");
978 let real_pkgs = tmp.path().join("real");
979 let pkg_dir = tmp.path().join("packages");
980 std::fs::create_dir_all(&pkg_dir).expect("create packages dir");
981
982 write_auto_library_init_lua(&real_pkgs, "path_dep_lib");
983
984 let real_dir = real_pkgs.join("path_dep_lib");
985 unix_symlink(&real_dir, pkg_dir.join("path_dep_lib")).expect("create alive symlink");
986
987 let canonical = real_dir.canonicalize().expect("canonicalize real dir");
989
990 let svc = make_app_service_at(tmp.path().to_path_buf()).await;
991 let registered = HashSet::new();
992 let registered_paths = vec![canonical];
993 let result = svc
994 .collect_alive_unregistered_symlinks(&pkg_dir, ®istered, ®istered_paths, None)
995 .await
996 .expect("helper should not error");
997
998 assert!(
999 result.is_empty(),
1000 "path-dep registered entry must not appear in result"
1001 );
1002 }
1003 }
1004}