use saya_agent::ToolError;
use saya_harness::workspace::Workspace;
const MARKER: &[u8] = b"[redacted]";
pub(super) const MARKER_PROBE_MAX_BYTES: u64 = saya_harness::workspace::patch::PATCH_MAX_FILE_BYTES;
pub(super) fn count_markers(bytes: &[u8]) -> usize {
let mut count = 0;
let mut cursor = 0;
while cursor + MARKER.len() <= bytes.len() {
if bytes[cursor..cursor + MARKER.len()] == *MARKER {
count += 1;
cursor += MARKER.len();
} else {
cursor += 1;
}
}
count
}
pub(super) fn existing_marker_count(
workspace: &Workspace,
rel: &str,
) -> Result<usize, saya_harness::HarnessError> {
match workspace.read(rel, MARKER_PROBE_MAX_BYTES) {
Ok(file) => Ok(count_markers(&file.bytes)),
Err(error) if error.is_not_found() => Ok(0),
Err(error) => Err(error),
}
}
pub(super) fn refuse_marker_growth(
path: &str,
before: usize,
new_content: &[u8],
map_error: impl FnOnce(String) -> ToolError,
) -> Result<(), ToolError> {
let after = count_markers(new_content);
if after > before {
return Err(map_error(format!(
"refused: the new content holds {after} occurrence(s) of the literal \
`[redacted]` marker, more than the {before} already on disk: {path}. This \
looks like a masked secret from a tool result being copied back verbatim, \
not a real edit — the real value on disk is unchanged. Edit around the \
masked span instead of retyping it."
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::count_markers;
#[test]
fn counts_non_overlapping_markers() {
assert_eq!(count_markers(b"no markers here"), 0);
assert_eq!(count_markers(b"a = f([redacted])"), 1);
assert_eq!(
count_markers(b"[redacted] and again [redacted]"),
2,
"two separate occurrences must both count"
);
}
#[test]
fn does_not_count_the_longer_private_key_marker() {
assert_eq!(count_markers(b"[redacted private key]"), 0);
}
}