Skip to content

Commit 47388ee

Browse files
committed
fix(tools): match rss scan cap precedence
Keep scan-byte and file-count caps off the per-line path so an exact-fill file is fully matched before the next file truncates, matching native.
1 parent e6ff324 commit 47388ee

2 files changed

Lines changed: 112 additions & 13 deletions

File tree

‎rss/tools/search_files.rss‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ fn push_match(state: map, line: string) -> map {
694694
state
695695
}
696696

697-
fn observe_limits(state: map) -> map {
697+
fn observe_controls(state: map) -> map {
698698
let tick: map = cap::clock_monotonic_ms(types::map_string(state, "token", ""));
699699
if map_bool(tick, "ok", false) == false {
700700
state.fatal = true;
@@ -715,14 +715,6 @@ fn observe_limits(state: map) -> map {
715715
}
716716
}
717717
}
718-
if map_int(state, "files_visited", 0) >= map_int(state, "max_search_files", 10000) {
719-
state.truncated = true;
720-
state.stop = true;
721-
}
722-
if map_int(state, "scanned_bytes", 0) >= map_int(state, "max_search_scanned_bytes", 16777216) {
723-
state.truncated = true;
724-
state.stop = true;
725-
}
726718
let checked: map = control_failure();
727719
if map_bool(checked, "ok", false) == false {
728720
state.fatal = true;
@@ -733,9 +725,17 @@ fn observe_limits(state: map) -> map {
733725
state
734726
}
735727

728+
fn observe_file_count(state: map) -> map {
729+
if map_int(state, "files_visited", 0) >= map_int(state, "max_search_files", 10000) {
730+
state.truncated = true;
731+
state.stop = true;
732+
}
733+
state
734+
}
735+
736736
fn walk_search(state: map, path: string, depth: int) -> map {
737737
state.dirs_visited = map_int(state, "dirs_visited", 0) + 1;
738-
state = observe_limits(state);
738+
state = observe_controls(state);
739739
if map_bool(state, "stop", false) == false {
740740
let remaining: int = map_int(state, "max_search_files", 10000) - map_int(state, "files_visited", 0);
741741
if remaining <= 0 {
@@ -810,8 +810,10 @@ fn walk_search(state: map, path: string, depth: int) -> map {
810810
}
811811
} else {
812812
if file_type == "file" {
813-
state = observe_limits(state);
813+
state = observe_controls(state);
814814
if map_bool(state, "stop", false) == false {
815+
state = observe_file_count(state);
816+
if map_bool(state, "stop", false) == false {
815817
state.files_visited = map_int(state, "files_visited", 0) + 1;
816818
if file_glob_ok(state, name, child) {
817819
let token: string = types::map_string(state, "token", "");
@@ -841,7 +843,7 @@ fn walk_search(state: map, path: string, depth: int) -> map {
841843
let lines: array = split_inclusive_newline(text);
842844
let mut line_index: int = 0;
843845
while map_bool(state, "stop", false) == false && line_index < lines.length {
844-
state = observe_limits(state);
846+
state = observe_controls(state);
845847
if map_bool(state, "stop", false) == false {
846848
let line: string = lines[line_index].copy();
847849
if string_contains(line, pattern) {
@@ -858,6 +860,7 @@ fn walk_search(state: map, path: string, depth: int) -> map {
858860
}
859861
}
860862
}
863+
}
861864
}
862865
}
863866
}
@@ -1028,7 +1031,7 @@ pub fn execute(context: map, arguments: map) -> map {
10281031
result = fail_host(start_clock);
10291032
} else {
10301033
state.start_ms = map_int(start_clock, "ms", 0);
1031-
state = observe_limits(state);
1034+
state = observe_controls(state);
10321035
if map_bool(state, "stop", false) == false {
10331036
state = walk_search(state, path, 0);
10341037
}

‎tests/rss_file_tool_tests.rs‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,102 @@ fn search_depth_rejected_child_dirs_are_not_counted() {
12101210
assert_exact_envelope(&native, &rss.result);
12111211
}
12121212

1213+
fn assert_search_exact_envelope(
1214+
label: &str,
1215+
files: &[(&str, &str)],
1216+
mut config_edit: impl FnMut(&mut FileToolConfig),
1217+
arguments: Value,
1218+
) -> (ToolResult, RssRun) {
1219+
let fixture = Fixture::new(label);
1220+
for (name, contents) in files {
1221+
fs::write(fixture.root.join(name), contents).unwrap();
1222+
}
1223+
let mut config = fixture.config();
1224+
config_edit(&mut config);
1225+
config.artifact_store.root = fixture.parent.join(format!("artifacts-{label}"));
1226+
let native = native_execute(
1227+
&fixture.tools_with_config(config.clone()),
1228+
NativeToolExecutor::SearchFiles,
1229+
&arguments,
1230+
);
1231+
let rss = run_rss_search(&fixture, &config, arguments);
1232+
assert_exact_envelope(&native, &rss.result);
1233+
(native, rss)
1234+
}
1235+
1236+
#[test]
1237+
fn search_exact_fill_scan_cap_matches_all_lines_without_truncation() {
1238+
let one_line = "needle\n";
1239+
let (native, rss) = assert_search_exact_envelope(
1240+
"search-exact-fill-one",
1241+
&[("exact.txt", one_line)],
1242+
|config| config.max_search_scanned_bytes = one_line.len(),
1243+
json!({"pattern": "needle"}),
1244+
);
1245+
assert!(native.ok, "native={native:?}");
1246+
assert!(!native.truncated, "native={native:?}");
1247+
assert_eq!(native.data["match_count"], json!(1));
1248+
assert_eq!(rss.result["truncated"], json!(false), "rss={}", rss.result);
1249+
assert_eq!(rss.result["data"]["match_count"], json!(1));
1250+
1251+
let multi = "n1\nn2\n";
1252+
let (native, rss) = assert_search_exact_envelope(
1253+
"search-exact-fill-multi",
1254+
&[("exact.txt", multi)],
1255+
|config| config.max_search_scanned_bytes = multi.len(),
1256+
json!({"pattern": "n"}),
1257+
);
1258+
assert!(native.ok, "native={native:?}");
1259+
assert!(!native.truncated, "native={native:?}");
1260+
assert_eq!(native.data["match_count"], json!(2));
1261+
assert_eq!(rss.result["truncated"], json!(false), "rss={}", rss.result);
1262+
assert_eq!(rss.result["data"]["match_count"], json!(2));
1263+
}
1264+
1265+
#[test]
1266+
fn search_exact_fill_then_later_positive_file_truncates_like_native() {
1267+
let first = "n1\nn2\n";
1268+
let (native, rss) = assert_search_exact_envelope(
1269+
"search-exact-fill-later",
1270+
&[("a.txt", first), ("b.txt", "n3\n")],
1271+
|config| config.max_search_scanned_bytes = first.len(),
1272+
json!({"pattern": "n"}),
1273+
);
1274+
assert!(native.ok, "native={native:?}");
1275+
assert!(native.truncated, "native={native:?}");
1276+
assert_eq!(native.data["match_count"], json!(2));
1277+
assert!(
1278+
native.content.contains("a.txt"),
1279+
"exact-fill file must match: native={native:?}"
1280+
);
1281+
assert!(
1282+
!native.content.contains("b.txt"),
1283+
"later file must not match after exact fill: native={native:?}"
1284+
);
1285+
assert_eq!(rss.result["truncated"], json!(true), "rss={}", rss.result);
1286+
assert_eq!(rss.result["data"]["match_count"], json!(2));
1287+
}
1288+
1289+
#[test]
1290+
fn search_final_files_visited_slot_is_fully_matched() {
1291+
let (native, rss) = assert_search_exact_envelope(
1292+
"search-final-file-slot",
1293+
&[("a.txt", "n0\n"), ("b.txt", "n1\nn2\n")],
1294+
|config| config.max_search_files = 4,
1295+
json!({"pattern": "n"}),
1296+
);
1297+
assert!(native.ok, "native={native:?}");
1298+
assert!(!native.truncated, "native={native:?}");
1299+
assert_eq!(native.data["files_visited"], json!(2));
1300+
assert_eq!(native.data["match_count"], json!(3));
1301+
assert!(
1302+
native.content.contains("b.txt:1:n1") && native.content.contains("b.txt:2:n2"),
1303+
"final files_visited slot must be fully matched: native={native:?}"
1304+
);
1305+
assert_eq!(rss.result["truncated"], json!(false), "rss={}", rss.result);
1306+
assert_eq!(rss.result["data"]["match_count"], json!(3));
1307+
}
1308+
12131309
fn write_search_files(fixture: &Fixture, relative_paths: &[&str]) {
12141310
for name in relative_paths {
12151311
let path = fixture.root.join(name);

0 commit comments

Comments
 (0)