Files
hermes-agent/tests/tools/test_write_verification.py
Teknium 2c8a932f80 feat(file): verify write_file content on disk and say so (verified: true)
write_file confirmed only SIZE (wc -c) after writing — never content.
Models compensated by re-reading files immediately after writing them
(154 verify-reads in a 400k-msg production window), and a corrupted
write (truncated pipe, backend FS oddity) could silently pass.

The write path now compares the on-disk sha256 against the intended
content (one shell call). Three outcomes:
- match -> result carries verified: true; the schema tells the model
  an explicit contract: do NOT re-read to check the write landed.
- mismatch -> hard error ('The write did not persist correctly'),
  mirroring patch_replace's existing post-write verification.
- backend can't hash (no sha256sum) -> flag omitted, write unaffected.

Hashes the shim-adjusted content (after CRLF/BOM preservation) so
Windows-line-ending and BOM round-trips verify correctly; surrogatepass
encoding matches the rest of the codebase's hashing of model text.
2026-08-02 15:12:23 -07:00

74 lines
2.7 KiB
Python

"""Tests for write_file post-write content verification (verified flag)."""
import json
from unittest.mock import patch as mock_patch
import pytest
from tools.file_tools import write_file_tool
@pytest.fixture
def workdir(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
return tmp_path
class TestWriteVerification:
def test_successful_write_reports_verified(self, workdir):
f = workdir / "out.txt"
r = json.loads(write_file_tool(str(f), "hello verified world\n", task_id="t-wv"))
assert r.get("bytes_written") == len("hello verified world\n")
assert r.get("verified") is True
def test_unicode_content_verified(self, workdir):
f = workdir / "uni.txt"
content = "línea → uno · ✓\n"
r = json.loads(write_file_tool(str(f), content, task_id="t-wv"))
assert r.get("verified") is True
def test_crlf_preservation_still_verifies(self, workdir):
# Existing CRLF file: write_file converts LF content to CRLF before
# writing; verification hashes the shim-adjusted content, so it must
# still report verified.
f = workdir / "win.txt"
f.write_bytes(b"old line\r\n")
r = json.loads(write_file_tool(str(f), "new line\nsecond\n", task_id="t-wv"))
assert "error" not in r
assert r.get("verified") is True
assert b"\r\n" in f.read_bytes()
def test_hash_mismatch_is_hard_error(self, workdir):
f = workdir / "bad.txt"
import tools.file_operations as fo
real_sha = fo.hashlib.sha256
class _WrongHash:
def __init__(self, *a, **k):
self._h = real_sha(b"different content entirely")
def hexdigest(self):
return self._h.hexdigest()
with mock_patch.object(fo.hashlib, "sha256", _WrongHash):
r = json.loads(write_file_tool(str(f), "actual content\n", task_id="t-wv"))
assert "error" in r
assert "did not persist" in r["error"]
def test_verification_failure_never_breaks_write(self, workdir):
# sha256sum unavailable/failing -> verified omitted, write still ok.
f = workdir / "ok.txt"
import tools.file_operations as fo
real_exec = fo.ShellFileOperations._exec
def flaky_exec(self, cmd, **kw):
if "sha256sum" in cmd:
raise RuntimeError("no hash binary")
return real_exec(self, cmd, **kw)
with mock_patch.object(fo.ShellFileOperations, "_exec", flaky_exec):
r = json.loads(write_file_tool(str(f), "content lands anyway\n", task_id="t-wv2"))
assert "error" not in r
assert f.read_text() == "content lands anyway\n"
assert "verified" not in r or r.get("verified") is None