fix: tolerate invalid UTF-8 in reader snapshots

Keep generation signatures readable across malformed physical rows so journal and tree readers can preserve valid entries while reporting partial coverage.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
Ouroboros 2026-08-21 17:55:57 +03:00
parent 4c52a80b6e
commit fcfe95884c
2 changed files with 39 additions and 1 deletions

View file

@ -82,7 +82,7 @@ def jsonl_generation_signature(path: pathlib.Path) -> dict:
return {}
try:
stat = path.stat()
with path.open("r", encoding="utf-8") as handle:
with path.open("r", encoding="utf-8", errors="replace") as handle:
first = next((line.strip() for line in handle if line.strip()), "")
return {
"first_line_sha256": hashlib.sha256(first.encode("utf-8", errors="replace")).hexdigest(),

View file

@ -96,6 +96,21 @@ def test_journal_read_preserves_valid_rows_but_marks_malformed_coverage(tmp_path
assert "2 unreadable" in digest
def test_journal_read_preserves_valid_rows_with_invalid_utf8(tmp_path, monkeypatch):
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path)
_journal_rows(tmp_path, "mine", 1)
path = tmp_path / "projects" / "mine" / "journal.jsonl"
with path.open("ab") as handle:
handle.write(b"\xfflegacy-row\n")
ctx = SimpleNamespace(drive_root=tmp_path / "fork", project_id="mine")
page = _journal_read(ctx, limit=2)
assert "journal-000" in page
assert "unreadable=1" in page
assert "coverage=partial" in page
def _tree_rows(root, root_id: str, count: int) -> None:
for index in range(count):
result = tree_ledger_append(
@ -191,6 +206,25 @@ def test_tree_read_preserves_valid_rows_but_marks_malformed_coverage(tmp_path):
assert "remaining=0" in second and "coverage=partial" in second
def test_tree_read_preserves_valid_rows_with_invalid_utf8(tmp_path):
_tree_rows(tmp_path, "root-task", 1)
path = tmp_path / "task_trees" / "root-task" / "blackboard.jsonl"
with path.open("ab") as handle:
handle.write(b"\xfflegacy-row\n")
ctx = SimpleNamespace(
task_id="child",
drive_root=tmp_path / "fork",
budget_drive_root=str(tmp_path),
task_metadata={"root_task_id": "root-task"},
)
page = _tree_read(ctx, limit=2)
assert "tree-000" in page
assert "unreadable=1" in page
assert "coverage=partial" in page
def test_runtime_tree_digest_uses_same_canonical_root_as_tree_read(tmp_path):
from ouroboros.context import build_runtime_section
@ -201,6 +235,9 @@ def test_runtime_tree_digest_uses_same_canonical_root_as_tree_read(tmp_path):
"root-task", "note", "canonical-passive-row", task_id="child",
data_root=canonical,
).startswith("OK:")
path = canonical / "task_trees" / "root-task" / "blackboard.jsonl"
with path.open("ab") as handle:
handle.write(b"\xfflegacy-row\n")
env = SimpleNamespace(
repo_dir=pathlib.Path(__file__).parents[1],
drive_root=fork,
@ -220,6 +257,7 @@ def test_runtime_tree_digest_uses_same_canonical_root_as_tree_read(tmp_path):
rendered = build_runtime_section(env, task, ctx=ctx)
assert "canonical-passive-row" in rendered
assert "coverage partial: 1 unreadable task-tree row" in rendered
def _write_task(root, index: int, *, project_id: str = "mine") -> None: