Add non-UTF-8 robustness fixtures

ober

05ee8b9e7087398cf6bf5e283a7851fad4110621

diff --git a/GAPS.md b/GAPS.md
index 6e6d26b..0f412ed 100644
--- a/GAPS.md
+++ b/GAPS.md
@@ -427,6 +427,12 @@ Acceptance criteria:
 - Ensure scans warn rather than crash.
 - Ensure metadata-only scanning still works.
 
+Status: implemented with smoke fixtures for invalid UTF-8 file contents,
+including recorded metadata-only scans. The test also attempts an invalid
+UTF-8 path fixture through Python byte paths when the platform/filesystem
+permits it, and asserts the scanner returns structured JSON with warnings
+instead of crashing.
+
 ### G-043: Prompt-injection/adversarial model tests missing
 
 The local provider adapter is bounded and explicit, but prompt injection in
diff --git a/tests/fixture-smoke.sh b/tests/fixture-smoke.sh
index 564ed1a..f6122b1 100755
--- a/tests/fixture-smoke.sh
+++ b/tests/fixture-smoke.sh
@@ -14,7 +14,8 @@ identity_fixture=$(mktemp -d)
 git_ai_note_fixture=$(mktemp -d)
 injection_fixture=$(mktemp -d)
 nul_path_fixture=$(mktemp -d)
-trap 'rm -rf "$fixture" "$shallow" "$provider_tmp" "$shape_fixture" "$timing_fixture" "$normal_timing_fixture" "$similarity_fixture" "$baseline_fixture" "$identity_fixture" "$git_ai_note_fixture" "$injection_fixture" "$nul_path_fixture"' EXIT
+non_utf8_fixture=$(mktemp -d)
+trap 'rm -rf "$fixture" "$shallow" "$provider_tmp" "$shape_fixture" "$timing_fixture" "$normal_timing_fixture" "$similarity_fixture" "$baseline_fixture" "$identity_fixture" "$git_ai_note_fixture" "$injection_fixture" "$nul_path_fixture" "$non_utf8_fixture"' EXIT
 
 git -C "$fixture" init -q
 git -C "$fixture" config user.name "Human Dev"
@@ -112,6 +113,61 @@ nul_file_json=$("$root/bin/jerboa-aigit" scan "$nul_path_fixture" --format json 
 printf '%s\n' "$nul_file_json" | grep -q 'src/tab\\tname\\nsnow-'
 printf '%s\n' "$nul_file_json" | grep -q '"count":1'
 
+git -C "$non_utf8_fixture" init -q
+git -C "$non_utf8_fixture" config user.name "Bytes Tester"
+git -C "$non_utf8_fixture" config user.email "bytes@example.test"
+printf 'base\n' > "$non_utf8_fixture/README.md"
+git -C "$non_utf8_fixture" add README.md
+GIT_AUTHOR_DATE='2026-07-29T09:01:50-06:00' \
+GIT_COMMITTER_DATE='2026-07-29T09:01:50-06:00' \
+  git -C "$non_utf8_fixture" commit -q -m 'base bytes fixture'
+printf '\377\376\375\000binary-ish invalid utf8\n' > "$non_utf8_fixture/invalid.bin"
+git -C "$non_utf8_fixture" add invalid.bin
+GIT_AUTHOR_DATE='2026-07-29T09:01:55-06:00' \
+GIT_COMMITTER_DATE='2026-07-29T09:01:55-06:00' \
+  git -C "$non_utf8_fixture" commit -q -m 'add invalid utf8 bytes'
+git -C "$non_utf8_fixture" notes --ref=ai add -m '{"tool":"codex","model":"gpt-5","lines":[{"path":"invalid.bin","start":1,"end":1}]}' HEAD
+non_utf8_json=$("$root/bin/jerboa-aigit" scan "$non_utf8_fixture" --format json --count 1)
+printf '%s\n' "$non_utf8_json" | grep -q '"recorded_ai_note_present":true'
+printf '%s\n' "$non_utf8_json" | grep -q 'binary file changes skipped'
+printf '%s\n' "$non_utf8_json" | grep -q 'no added UTF-8 patch lines available'
+non_utf8_metadata_json=$("$root/bin/jerboa-aigit" scan "$non_utf8_fixture" --format json --count 1 --metadata-only)
+printf '%s\n' "$non_utf8_metadata_json" | grep -q '"signals":\[\]'
+printf '%s\n' "$non_utf8_metadata_json" | grep -q '"recorded_ai_note_present":true'
+
+if command -v python3 >/dev/null 2>&1; then
+  invalid_path_fixture="$provider_tmp/invalid-path-repo"
+  mkdir -p "$invalid_path_fixture"
+  git -C "$invalid_path_fixture" init -q
+  git -C "$invalid_path_fixture" config user.name "Path Bytes"
+  git -C "$invalid_path_fixture" config user.email "path-bytes@example.test"
+  printf 'base\n' > "$invalid_path_fixture/README.md"
+  git -C "$invalid_path_fixture" add README.md
+  GIT_AUTHOR_DATE='2026-07-29T09:01:56-06:00' \
+  GIT_COMMITTER_DATE='2026-07-29T09:01:56-06:00' \
+    git -C "$invalid_path_fixture" commit -q -m 'base invalid path fixture'
+  if python3 - "$invalid_path_fixture" <<'PY'
+import os, sys
+root = os.fsencode(sys.argv[1])
+path = os.path.join(root, b"invalid-\xff.py")
+try:
+    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
+    os.write(fd, b'print("invalid path bytes")\n')
+    os.close(fd)
+except Exception:
+    sys.exit(1)
+PY
+  then
+    git -C "$invalid_path_fixture" add -A
+    GIT_AUTHOR_DATE='2026-07-29T09:01:57-06:00' \
+    GIT_COMMITTER_DATE='2026-07-29T09:01:57-06:00' \
+      git -C "$invalid_path_fixture" commit -q -m 'add invalid utf8 path bytes'
+    invalid_path_json=$("$root/bin/jerboa-aigit" scan "$invalid_path_fixture" --format json --count 1)
+    printf '%s\n' "$invalid_path_json" | grep -q '"count":1'
+    printf '%s\n' "$invalid_path_json" | grep -q '"warnings":'
+  fi
+fi
+
 git -C "$git_ai_note_fixture" init -q
 git -C "$git_ai_note_fixture" config user.name "Human Dev"
 git -C "$git_ai_note_fixture" config user.email "human@example.test"