ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
authored_token_census.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Git-authored file census and encoding-independent rare-token search."""
4
5from __future__ import annotations
6
7import os
8import stat
9import subprocess
10from collections.abc import Callable, Collection
11from dataclasses import dataclass
12from pathlib import Path, PurePosixPath
13from tempfile import TemporaryDirectory
14
15from git_environment import LOCAL_GIT_ENVIRONMENT, isolated_git_environment, trusted_git_executable
16
17BATCH_HEADER_FIELDS = 3
18REGULAR_INDEX_MODES = frozenset({"100644", "100755"})
19TOKEN_ENCODINGS = ("utf-8", "utf-16-le", "utf-16-be")
20SELFTEST_TOKEN = "repair-" + "entry"
21
22
23class CensusError(RuntimeError):
24 """Raised when Git or the worktree cannot prove an authored-file census."""
25
26
27@dataclass(frozen=True)
28class IndexEntry:
29 """One stage-zero regular-file candidate from the inherited live index."""
30
31 relative: str
32 mode: str
33 object_id: str
34
35
36@dataclass(frozen=True)
37class AuthoredSource:
38 """One independently validated index or worktree byte source."""
39
40 relative: str
41 view: str
42 data: bytes
43
44
45GitRunner = Callable[[list[str], Path, bytes | None], subprocess.CompletedProcess[bytes]]
46
47
48def _default_git_runner(
49 argv: list[str], cwd: Path, input_data: bytes | None
50) -> subprocess.CompletedProcess[bytes]:
51 """Run one non-mutating Git census command."""
52 return subprocess.run( # noqa: S603 -- argv is the fixed Git census below
53 argv, cwd=cwd, input=input_data, capture_output=True, check=False
54 )
55
56
57def _git_output(
58 repo_root: Path,
59 args: tuple[str, ...],
60 runner: GitRunner = _default_git_runner,
61 input_data: bytes | None = None,
62) -> bytes:
63 """Return warning-free Git output or fail closed."""
64 argv = [trusted_git_executable(), *args]
65 try:
66 result = runner(argv, repo_root, input_data)
67 except OSError as exc:
68 message = f"Git authored-file census could not start: {exc}"
69 raise CensusError(message) from exc
70 stderr = result.stderr.decode("utf-8", errors="replace").strip()
71 if result.returncode != 0:
72 detail = stderr or f"exit {result.returncode}"
73 message = f"Git authored-file census failed: {detail}"
74 raise CensusError(message)
75 if stderr:
76 message = f"Git authored-file census warned: {stderr}"
77 raise CensusError(message)
78 return result.stdout
79
80
81def _nul_records(output: bytes) -> tuple[bytes, ...]:
82 """Parse complete NUL-delimited Git records fail closed."""
83 if not output:
84 return ()
85 if not output.endswith(b"\0"):
86 message = "Git authored-file census returned a truncated record"
87 raise CensusError(message)
88 records = tuple(output[:-1].split(b"\0"))
89 if any(not record for record in records):
90 message = "Git authored-file census returned an empty path"
91 raise CensusError(message)
92 return records
93
94
95def _decode_git_path(raw: bytes) -> str:
96 """Decode and constrain one repository-relative Git path."""
97 try:
98 relative = raw.decode("utf-8")
99 except UnicodeDecodeError as exc:
100 message = "Git authored-file census found a non-UTF-8 path"
101 raise CensusError(message) from exc
102 pure = PurePosixPath(relative)
103 if not relative or pure.is_absolute() or ".." in pure.parts or pure.as_posix() != relative:
104 message = f"Git authored-file census returned unsafe path {relative!r}"
105 raise CensusError(message)
106 return relative
107
108
109def _git_paths(repo_root: Path, args: tuple[str, ...]) -> tuple[str, ...]:
110 """Return unique decoded paths from one Git query."""
111 records = _nul_records(_git_output(repo_root, args))
112 paths = tuple(_decode_git_path(record) for record in records)
113 if len(paths) != len(set(paths)):
114 message = "Git authored-file census returned duplicate paths"
115 raise CensusError(message)
116 return paths
117
118
119def _git_index_entries(repo_root: Path) -> tuple[IndexEntry, ...]:
120 """Parse the inherited live index, rejecting conflicts and duplicates."""
121 entries = []
122 seen = set()
123 output = _git_output(repo_root, ("ls-files", "-z", "--stage"))
124 for record in _nul_records(output):
125 try:
126 metadata, raw_path = record.split(b"\t", 1)
127 raw_mode, raw_object_id, raw_stage = metadata.split(b" ", 2)
128 mode = raw_mode.decode("ascii")
129 object_id = raw_object_id.decode("ascii")
130 except ValueError as exc:
131 message = "Git authored-file census returned malformed stage data"
132 raise CensusError(message) from exc
133 except UnicodeDecodeError as exc:
134 message = "Git authored-file census returned non-ASCII stage metadata"
135 raise CensusError(message) from exc
136 relative = _decode_git_path(raw_path)
137 if raw_stage != b"0":
138 message = f"Git authored-file census found an unresolved index stage: {relative}"
139 raise CensusError(message)
140 if relative in seen:
141 message = f"Git authored-file census returned a duplicate index path: {relative}"
142 raise CensusError(message)
143 if len(object_id) not in {40, 64} or any(
144 character not in "0123456789abcdef" for character in object_id
145 ):
146 message = f"Git authored-file census returned an invalid object ID: {relative}"
147 raise CensusError(message)
148 seen.add(relative)
149 entries.append(IndexEntry(relative, mode, object_id))
150 return tuple(entries)
151
152
153def _scoped_index_entries(
154 entries: Collection[IndexEntry], excluded_parts: Collection[str]
155) -> tuple[IndexEntry, ...]:
156 """Apply explicit tree exclusions, then require regular index modes."""
157 scoped = []
158 for entry in entries:
159 if any(part in excluded_parts for part in PurePosixPath(entry.relative).parts):
160 continue
161 if entry.mode == "120000":
162 message = f"first-party authored path is an index symlink: {entry.relative}"
163 raise CensusError(message)
164 if entry.mode not in REGULAR_INDEX_MODES:
165 message = f"first-party authored path has unsupported index mode: {entry.relative}"
166 raise CensusError(message)
167 scoped.append(entry)
168 return tuple(scoped)
169
170
171def _parse_blob_batch(output: bytes, entries: Collection[IndexEntry]) -> tuple[bytes, ...]:
172 """Parse exact `git cat-file --batch` blob records fail closed."""
173 cursor = 0
174 blobs = []
175 for entry in entries:
176 line_end = output.find(b"\n", cursor)
177 if line_end < 0:
178 message = "Git authored-file blob batch returned a truncated header"
179 raise CensusError(message)
180 header = output[cursor:line_end].split(b" ")
181 if (
182 len(header) != BATCH_HEADER_FIELDS
183 or header[0] != entry.object_id.encode()
184 or header[1] != b"blob"
185 ):
186 message = f"Git authored-file blob batch returned wrong metadata: {entry.relative}"
187 raise CensusError(message)
188 try:
189 size = int(header[2])
190 except ValueError as exc:
191 message = f"Git authored-file blob batch returned an invalid size: {entry.relative}"
192 raise CensusError(message) from exc
193 start = line_end + 1
194 end = start + size
195 if size < 0 or end >= len(output) or output[end : end + 1] != b"\n":
196 message = f"Git authored-file blob batch truncated content: {entry.relative}"
197 raise CensusError(message)
198 blobs.append(output[start:end])
199 cursor = end + 1
200 if cursor != len(output):
201 message = "Git authored-file blob batch returned trailing data"
202 raise CensusError(message)
203 return tuple(blobs)
204
205
206def _index_blobs(repo_root: Path, entries: Collection[IndexEntry]) -> tuple[bytes, ...]:
207 """Read all inherited-index blobs in one warning-free Git process."""
208 if not entries:
209 return ()
210 request = b"".join(f"{entry.object_id}\n".encode() for entry in entries)
211 output = _git_output(repo_root, ("cat-file", "--batch"), input_data=request)
212 return _parse_blob_batch(output, entries)
213
214
215def path_lstat(path: Path) -> os.stat_result:
216 """Read one worktree path without following a symbolic link."""
217 return path.lstat()
218
219
220def path_read_bytes(path: Path) -> bytes:
221 """Read one proven-regular authored file as uninterpreted bytes."""
222 return path.read_bytes()
223
224
225def read_file(path: Path, reader: Callable[[Path], bytes] = path_read_bytes) -> bytes:
226 """Read one authored file and convert every I/O failure into census failure."""
227 try:
228 return reader(path)
229 except OSError as exc:
230 message = f"authored file cannot be read: {path}: {exc}"
231 raise CensusError(message) from exc
232
233
234def _authored_inventory(
235 repo_root: Path,
236 excluded_parts: Collection[str],
237 lstat_file: Callable[[Path], os.stat_result] = path_lstat,
238) -> tuple[tuple[IndexEntry, ...], tuple[str, ...]]:
239 """Return validated index entries and present nonignored worktree paths."""
240 entries = _scoped_index_entries(_git_index_entries(repo_root), excluded_parts)
241 untracked = _git_paths(repo_root, ("ls-files", "-z", "--others", "--exclude-standard"))
242 untracked = tuple(
243 relative
244 for relative in untracked
245 if not any(part in excluded_parts for part in PurePosixPath(relative).parts)
246 )
247 index_paths = {entry.relative for entry in entries}
248 worktree = []
249 for relative in sorted(index_paths | set(untracked)):
250 path = repo_root / relative
251 try:
252 path_stat = lstat_file(path)
253 except FileNotFoundError as exc:
254 if relative in index_paths:
255 continue
256 message = f"authored path disappeared during census: {relative}"
257 raise CensusError(message) from exc
258 except OSError as exc:
259 message = f"cannot inspect authored path {relative}: {exc}"
260 raise CensusError(message) from exc
261 if stat.S_ISLNK(path_stat.st_mode):
262 message = f"first-party authored path is a worktree symlink: {relative}"
263 raise CensusError(message)
264 if not stat.S_ISREG(path_stat.st_mode):
265 message = f"first-party authored path is not a regular file: {relative}"
266 raise CensusError(message)
267 worktree.append(relative)
268 return entries, tuple(worktree)
269
270
271def authored_files(
272 repo_root: Path,
273 excluded_parts: Collection[str],
274 lstat_file: Callable[[Path], os.stat_result] = path_lstat,
275) -> list[Path]:
276 """Return the union of live-index and nonignored worktree paths."""
277 entries, worktree = _authored_inventory(repo_root, excluded_parts, lstat_file)
278 relative_paths = {entry.relative for entry in entries} | set(worktree)
279 return [repo_root / relative for relative in sorted(relative_paths)]
280
281
282def authored_sources(
283 repo_root: Path,
284 excluded_parts: Collection[str],
285 lstat_file: Callable[[Path], os.stat_result] = path_lstat,
286 reader: Callable[[Path], bytes] = path_read_bytes,
287) -> tuple[AuthoredSource, ...]:
288 """Read inherited-index blobs and present worktree bytes independently."""
289 entries, worktree = _authored_inventory(repo_root, excluded_parts, lstat_file)
290 sources = [
291 AuthoredSource(entry.relative, "index", data)
292 for entry, data in zip(entries, _index_blobs(repo_root, entries), strict=True)
293 ]
294 for relative in worktree:
295 data = read_file(repo_root / relative, reader)
296 sources.append(AuthoredSource(relative, "worktree", data))
297 return tuple(sources)
298
299
300def token_hits(
301 data: bytes,
302 tokens: Collection[str],
303 encodings: Collection[str] = TOKEN_ENCODINGS,
304) -> tuple[str, ...]:
305 """Find rare tokens in each explicitly supported text encoding."""
306 return tuple(
307 token for token in tokens if any(token.encode(encoding) in data for encoding in encodings)
308 )
309
310
311def init_test_repo(repo_root: Path) -> None:
312 """Initialize one throwaway Git repository for semantic selftests."""
313 _run_test_git(repo_root, "init", "-q")
314
315
316def _run_test_git(repo_root: Path, *args: str) -> None:
317 """Run one mutating Git command inside a throwaway selftest repository."""
318 subprocess.run( # noqa: S603 -- fixed Git selftest command
319 [trusted_git_executable(), *args],
320 cwd=repo_root,
321 capture_output=True,
322 check=True,
323 )
324
325
326def _write(repo_root: Path, relative: str, data: bytes = b"fixture\n") -> Path:
327 """Write one throwaway census fixture."""
328 path = repo_root / relative
329 path.parent.mkdir(parents=True, exist_ok=True)
330 path.write_bytes(data)
331 return path
332
333
334def _raises_census(operation: Callable[[], object]) -> bool:
335 """Return whether one semantic selftest operation fails closed."""
336 try:
337 operation()
338 except CensusError:
339 return True
340 return False
341
342
343def _source_hits(repo_root: Path, excluded_parts: Collection[str]) -> set[tuple[str, str]]:
344 """Return path/view pairs containing the synthetic rare token."""
345 return {
346 (source.relative, source.view)
347 for source in authored_sources(repo_root, excluded_parts)
348 if token_hits(source.data, (SELFTEST_TOKEN,))
349 }
350
351
352def _selftest_index_worktree_views(excluded_parts: Collection[str]) -> list[str]:
353 """Prove index and worktree bytes are scanned as independent views."""
354 token = SELFTEST_TOKEN.encode()
355 cases = (
356 ("staged unsafe/worktree safe", token, b"safe\n", {("entry", "index")}),
357 ("index unsafe/worktree deleted", token, None, {("entry", "index")}),
358 ("index safe/worktree unsafe", b"safe\n", token, {("entry", "worktree")}),
359 )
360 failures = []
361 for name, index_data, worktree_data, expected in cases:
362 with TemporaryDirectory() as tmp:
363 root = Path(tmp)
364 init_test_repo(root)
365 entry = _write(root, "entry", index_data)
366 _run_test_git(root, "add", "entry")
367 if worktree_data is None:
368 entry.unlink()
369 else:
370 entry.write_bytes(worktree_data)
371 if _source_hits(root, excluded_parts) != expected:
372 failures.append(f" {name} did not preserve both byte views")
373 return failures
374
375
376def _selftest_scope(excluded_parts: Collection[str]) -> list[str]:
377 """Prove Git inventory, exclusions, ignores and deletion semantics."""
378 with TemporaryDirectory() as tmp:
379 root = Path(tmp)
380 init_test_repo(root)
381 _write(root, ".gitignore", b"/build-cov/\n")
382 _write(root, "tracked.txt")
383 _write(root, "deleted.txt")
384 excluded = tuple(f"nested/{part}/ignored" for part in sorted(excluded_parts))
385 for relative in excluded:
386 _write(root, relative, SELFTEST_TOKEN.encode())
387 tracked_excluded = tuple(path for path in excluded if "/.git/" not in path)
388 _run_test_git(root, "add", "-f", ".gitignore", "tracked.txt", "deleted.txt")
389 if tracked_excluded:
390 _run_test_git(root, "add", "-f", "--", *tracked_excluded)
391 _run_test_git(
392 root,
393 "-c",
394 "user.name=fixture",
395 "-c",
396 "user.email=fixture@example.invalid",
397 "commit",
398 "-qm",
399 "fixture",
400 )
401 _run_test_git(root, "rm", "-q", "deleted.txt")
402 _write(root, "untracked.txt")
403 _write(root, "build-cov/ignored", SELFTEST_TOKEN.encode())
404 inputs = {
405 path.relative_to(root).as_posix() for path in authored_files(root, excluded_parts)
406 }
407 required = {".gitignore", "tracked.txt", "untracked.txt"}
408 forbidden = {*excluded, "deleted.txt", "build-cov/ignored"}
409 if not required <= inputs or forbidden & inputs:
410 return [" Git census did not preserve tracked/untracked/ignored/deleted scope"]
411 return []
412
413
414def _selftest_symlinks(excluded_parts: Collection[str]) -> list[str]:
415 """Prove worktree/index aliases fail while excluded vendor aliases stay out."""
416 failures = []
417 with TemporaryDirectory() as tmp:
418 root = Path(tmp)
419 init_test_repo(root)
420 _write(root, "vendor/target")
421 (root / "alias").symlink_to("vendor/target")
422 if not _raises_census(lambda: authored_files(root, excluded_parts)):
423 failures.append(" an untracked first-party worktree symlink was accepted")
424 with TemporaryDirectory() as tmp:
425 root = Path(tmp)
426 init_test_repo(root)
427 _write(root, "vendor/target")
428 alias = root / "alias"
429 alias.symlink_to("vendor/target")
430 _run_test_git(root, "add", "alias")
431 alias.unlink()
432 alias.write_text("regular replacement\n", encoding="utf-8")
433 if not _raises_census(lambda: authored_files(root, excluded_parts)):
434 failures.append(" a first-party index symlink was accepted after worktree replacement")
435 with TemporaryDirectory() as tmp:
436 root = Path(tmp)
437 init_test_repo(root)
438 _write(root, "vendor/target")
439 alias = _write(root, "alias")
440 _run_test_git(root, "add", "alias")
441 alias.unlink()
442 alias.symlink_to("vendor/target")
443 if not _raises_census(lambda: authored_sources(root, excluded_parts)):
444 failures.append(" a worktree symlink was accepted over a regular index entry")
445 with TemporaryDirectory() as tmp:
446 root = Path(tmp)
447 init_test_repo(root)
448 _write(root, "vendor/target")
449 (root / "vendor/alias").symlink_to("target")
450 _run_test_git(root, "add", "-f", "vendor/target", "vendor/alias")
451 if _raises_census(lambda: authored_files(root, excluded_parts)):
452 failures.append(" an explicitly excluded vendor symlink entered first-party scope")
453 return failures
454
455
456def _selftest_encodings(excluded_parts: Collection[str]) -> list[str]:
457 """Prove UTF-8, UTF-16LE, UTF-16BE and BOM token discovery end to end."""
458 cases = {
459 "utf8": SELFTEST_TOKEN.encode("utf-8"),
460 "utf16le": SELFTEST_TOKEN.encode("utf-16-le"),
461 "utf16be": SELFTEST_TOKEN.encode("utf-16-be"),
462 "utf16bom": b"\xff\xfe" + SELFTEST_TOKEN.encode("utf-16-le"),
463 }
464 failures = []
465 for name, data in cases.items():
466 with TemporaryDirectory() as tmp:
467 root = Path(tmp)
468 init_test_repo(root)
469 relative = f"{name}.blob"
470 _write(root, relative, data)
471 _run_test_git(root, "add", relative)
472 expected = {(relative, "index"), (relative, "worktree")}
473 if _source_hits(root, excluded_parts) != expected:
474 failures.append(f" authored {name} rare token was not detected")
475 return failures
476
477
478def _commit_fixture(repo_root: Path) -> None:
479 """Commit the current throwaway index with local synthetic identity."""
480 _run_test_git(
481 repo_root,
482 "-c",
483 "user.name=fixture",
484 "-c",
485 "user.email=fixture@example.invalid",
486 "commit",
487 "-qm",
488 "fixture",
489 )
490
491
492def _selftest_staged_deletions(excluded_parts: Collection[str]) -> list[str]:
493 """Prove staged deletion drops only index data while retained bytes stay scanned."""
494 failures = []
495 with TemporaryDirectory() as tmp:
496 root = Path(tmp)
497 init_test_repo(root)
498 _write(root, "entry", SELFTEST_TOKEN.encode())
499 _run_test_git(root, "add", "entry")
500 _commit_fixture(root)
501 _run_test_git(root, "rm", "-q", "entry")
502 if any(source.relative == "entry" for source in authored_sources(root, excluded_parts)):
503 failures.append(" staged deletion remained in an authored byte view")
504 with TemporaryDirectory() as tmp:
505 root = Path(tmp)
506 init_test_repo(root)
507 _write(root, "entry", SELFTEST_TOKEN.encode())
508 _run_test_git(root, "add", "entry")
509 _commit_fixture(root)
510 _run_test_git(root, "rm", "--cached", "-q", "entry")
511 if _source_hits(root, excluded_parts) != {("entry", "worktree")}:
512 failures.append(" staged index deletion hid retained nonignored worktree bytes")
513 return failures
514
515
516def _failed_git_runner(
517 argv: list[str], _cwd: Path, _input: bytes | None
518) -> subprocess.CompletedProcess[bytes]:
519 """Return one synthetic failing Git result."""
520 return subprocess.CompletedProcess(argv, 128, b"", b"fatal")
521
522
523def _warned_git_runner(
524 argv: list[str], _cwd: Path, _input: bytes | None
525) -> subprocess.CompletedProcess[bytes]:
526 """Return a success status carrying an unreadable-directory warning."""
527 warning = b"warning: could not open directory: Permission denied"
528 return subprocess.CompletedProcess(argv, 0, b"", warning)
529
530
531def _denied_git_runner(
532 _argv: list[str], _cwd: Path, _input: bytes | None
533) -> subprocess.CompletedProcess[bytes]:
534 """Raise the execution failure used by the Git fail-closed test."""
535 message = "fixture denied"
536 raise PermissionError(message)
537
538
539def _denied_path(_path: Path) -> bytes:
540 """Raise the permission failure used by stat/read fail-closed tests."""
541 message = "fixture denied"
542 raise PermissionError(message)
543
544
545def _selftest_fail_closed(excluded_parts: Collection[str]) -> list[str]:
546 """Prove Git warnings/failures and stat/read errors cannot shrink the scan."""
547 failures = []
548 if not _raises_census(lambda: _git_output(Path.cwd(), ("ls-files",), _failed_git_runner)):
549 failures.append(" a failing Git authored census was accepted")
550 if not _raises_census(lambda: _git_output(Path.cwd(), ("ls-files",), _warned_git_runner)):
551 failures.append(" an unreadable-directory Git warning was accepted")
552 if not _raises_census(lambda: _git_output(Path.cwd(), ("ls-files",), _denied_git_runner)):
553 failures.append(" a Git execution error was accepted")
554 with TemporaryDirectory() as tmp:
555 root = Path(tmp)
556 init_test_repo(root)
557 file_path = _write(root, "blocked")
558 if not _raises_census(lambda: authored_files(root, excluded_parts, _denied_path)):
559 failures.append(" an authored lstat error was accepted")
560 if not _raises_census(lambda: authored_sources(root, excluded_parts, reader=_denied_path)):
561 failures.append(f" an authored read error was accepted for {file_path.name}")
562 return failures
563
564
565def _fixture_selftests(excluded_parts: Collection[str]) -> list[str]:
566 """Run every nested-repository semantic fixture in the current environment."""
567 return (
568 _selftest_index_worktree_views(excluded_parts)
569 + _selftest_scope(excluded_parts)
570 + _selftest_symlinks(excluded_parts)
571 + _selftest_encodings(excluded_parts)
572 + _selftest_staged_deletions(excluded_parts)
573 + _selftest_fail_closed(excluded_parts)
574 )
575
576
577def _repo_snapshot(repo_root: Path) -> tuple[bytes, bytes, bytes]:
578 """Capture the outer state that a nested fixture must not mutate."""
579 with isolated_git_environment():
580 return (
581 _git_output(repo_root, ("rev-parse", "HEAD")),
582 (repo_root / ".git" / "index").read_bytes(),
583 _git_output(repo_root, ("status", "--porcelain=v1", "-z")),
584 )
585
586
587def _selftest_hostile_environment(excluded_parts: Collection[str]) -> list[str]:
588 """Prove hook-local Git routing cannot capture nested census fixtures."""
589 failures = []
590 with TemporaryDirectory() as tmp:
591 outer = Path(tmp)
592 with isolated_git_environment():
593 init_test_repo(outer)
594 _write(outer, "outer-sentinel")
595 _run_test_git(outer, "add", "outer-sentinel")
596 _run_test_git(
597 outer,
598 "-c",
599 "user.name=fixture",
600 "-c",
601 "user.email=fixture@example.invalid",
602 "commit",
603 "-qm",
604 "outer",
605 )
606 before = _repo_snapshot(outer)
607 original = {name: os.environ.get(name) for name in LOCAL_GIT_ENVIRONMENT}
608 hostile = dict.fromkeys(LOCAL_GIT_ENVIRONMENT, "hostile")
609 hostile.update(
610 {
611 "GIT_DIR": str(outer / ".git"),
612 "GIT_WORK_TREE": str(outer),
613 "GIT_INDEX_FILE": str(outer / ".git" / "index"),
614 "GIT_PREFIX": "",
615 }
616 )
617 try:
618 os.environ.update(hostile)
619 with isolated_git_environment():
620 if any(os.environ.get(name) == "hostile" for name in LOCAL_GIT_ENVIRONMENT):
621 failures.append(" nested census retained a hostile Git routing/config value")
622 failures.extend(_fixture_selftests(excluded_parts))
623 finally:
624 for name, value in original.items():
625 if value is None:
626 os.environ.pop(name, None)
627 else:
628 os.environ[name] = value
629 if _repo_snapshot(outer) != before:
630 failures.append(" nested census changed the hostile outer Git repository")
631 return failures
632
633
634def selftest(excluded_parts: Collection[str]) -> list[str]:
635 """Prove the Git-authored census and raw-token detector in both directions."""
636 with isolated_git_environment():
637 failures = _fixture_selftests(excluded_parts)
638 return failures + _selftest_hostile_environment(excluded_parts)