///|
// paths is the sorted query set; allowed_paths is the independent host list.
// Both have already passed exact_paths before any filesystem/rule access.
async fn select_exact(
root : String,
options : SelectionOptions,
paths : Array[String],
allowed_paths : Array[String]?,
max_files : Int32,
) -> SelectionExplanation {
let filter = make_path_filter(root, options, allowed_paths)
let files : Array[Candidate] = []
let skipped : Array[Skipped] = []
let decisions : Array[PathDecision] = []
for relative in paths {
match filter.decision(relative, true) {
Some(exclusion) => {
skipped.push({ path: relative, reason: exclusion.reason(), })
decisions.push(exclusion.decision(root, relative))
}
None => {
if files.length() >= max_files {
raise SelectionError::FileLimitExceeded(limit=max_files)
}
files.push({ path: join(root, relative), relative, })
decisions.push(candidate_decision(relative))
}
}
}
{ selection: { root, files, skipped, }, decisions, }
}
///|
/// Explain only literal requested paths, without recursion or candidate reads.
/// file_paths remains an independent host allowlist; Some([]) allows nothing.
/// Returns the same candidates for the caller to read once, without reselecting.
/// Decisions expose safe provenance; selection.skipped retains legacy diagnostics.
pub async fn explain(
root : String,
options : SelectionOptions,
paths : Array[String],
file_paths~ : Array[String]? = None,
max_files~ : Int32 = 2147483647,
) -> SelectionExplanation {
if max_files < 0 {
raise SelectionError::FileLimitExceeded(limit=max_files)
}
let paths = exact_paths(Some(paths)).unwrap()
let allowed_paths = exact_paths(file_paths)
let root = selection_root(root)
select_exact(root, options, paths, allowed_paths, max_files)
}