Files
ledger d0ba06e7de volt: Q89 closure -- the import-time-I/O detector is a shared instrument now, not three copies of a name list
The three refactor harnesses (probe, dump, pending) each carried the same
_IO_CALLS name list plus a builtins.open spy. Measured at bb4a85e in a clean
clone: Path(__file__).read_text(), os.open(__file__, O_RDONLY), io.open(__file__)
and with Path(__file__).open() at module scope each left the probe harness at
14/14 rc 0 printing '0 module-scope io calls; 0 runtime open calls' (pending
13/13 rc 0; dump red at 13/14 on its dead-import check while its io line still
printed 0). The name-list walk resolves Path(p).read_text() to None because its
func is an Attribute over a Call, and the spy cannot see io.open/pathlib.

Now: wizard-work/forge/import_io_detector.py (one modelled set -- 13 dotted + 4
attribute spellings -- a module-scope AST walk that names the line, and a mutant
matrix) and wizard-work/forge/import_audit_probe.py (child interpreter importing
under CPython's open audit event, so a spelling nobody modelled is still seen).
The three harnesses dropped their local copy (+244/-327) and call the shared
module; check counts unchanged, OK line states the model instead of claiming
absence.

Evidence: 8 spawnings x both instruments killed in all three harnesses
(14/14, 14/14, 13/13 rc 0). Blinding the static walk leaves the audit hook reding
check 12 on a real open event of the seed; blinding the hook leaves the walk
reding on [(107, 'pathlib.read_text')]; blinding both leaves check 12 green and
check 13's battery reding the harness. _F = open / _F(DEFAULT_PATH) -- a spelling
no list names -- is caught by the hook alone.

Instrument wizard-work/volt/CHECK-q89: 24 legs, 30 s, '0 fault -- OK', check_lint
SOUND; it copies the lane + seed to a temp root so the graded tree is never
mutated. Fourth copy (verify_dump_seed_refactor.py) untouched at 16/16 and used
as an independent oracle: agrees 12/12. Q90's beam guard untouched.
verify.py --fast: 10 legs, 9 pass, 0 fault, 1 skipped, gate OK.
2026-10-30 23:13:20 +00:00

113 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Child interpreter for ``import_io_detector.audit_reads``.
Import ``MODULE_PATH`` (argv[1]) with an audit hook on CPython's ``open`` event
and print ONE json object on stdout::
{"target": "<realpath>", "events": [{"path": ..., "mode": ..., "flags": ...}],
"import_error": null | "Type: message"}
Exit is 0 whenever that json was printed -- an import that raised is *reported*
in ``import_error``, not treated as a probe failure, because a mutant may raise
after the read and the read is what is being graded.
Why a child process: ``sys.addaudithook`` cannot be uninstalled, so an
in-process hook would keep watching the harness for the rest of its life.
What is filtered, and why (each rule is a claim, stated so it can be argued
with):
* the target module's own source and bytecode -- the import itself
* ``.py``/``.pyc``/``.pyo``/``.pyd``/``.so`` open from a directory on
``sys.path`` or under the interpreter's prefix -- the import
machinery loading code, not the module reading data
* opens whose mode/flags are write-only -- this grades reads;
``sys.dont_write_bytecode`` is set so a ``__pycache__`` write would not
even be attempted
Everything else is reported: a data file, a config, a seed, whatever spelling
of ``open`` produced it.
"""
from __future__ import annotations
import importlib.util
import json
import os
import sys
sys.dont_write_bytecode = True
TARGET = os.path.realpath(sys.argv[1])
CODE_EXT = (".py", ".pyc", ".pyo", ".pyd", ".so")
_DIRS = sorted({
os.path.realpath(d)
for d in [*sys.path, sys.base_prefix, sys.prefix]
if d and os.path.isdir(d)
})
def _is_code(path: str) -> bool:
"""True when this open is the import machinery loading code."""
real = os.path.realpath(path)
if real in (TARGET, TARGET + "c"):
return True
if real.endswith(CODE_EXT):
return any(real == d or real.startswith(d + os.sep) for d in _DIRS)
return False
def _is_write(mode, flags) -> bool:
"""True when the open cannot be a read (so it is out of this probe's scope)."""
if isinstance(mode, str) and mode:
return any(c in mode for c in "wax")
try:
value = int(flags)
except (TypeError, ValueError):
return False
return bool(value & (os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_APPEND))
def main() -> int:
events: list = []
def hook(event, args):
if event != "open":
return
path = args[0] if args else None
mode = args[1] if len(args) > 1 else None
flags = args[2] if len(args) > 2 else None
if path is None or _is_write(mode, flags):
return
try:
spelling = os.fspath(path)
except TypeError:
spelling = str(path)
if isinstance(spelling, bytes):
spelling = spelling.decode("utf-8", "replace")
if _is_code(spelling):
return
events.append({
"path": spelling,
"mode": mode if isinstance(mode, str) else None,
"flags": flags if isinstance(flags, int) else None,
})
sys.addaudithook(hook)
error = None
try:
spec = importlib.util.spec_from_file_location("_audited_import", TARGET)
module = importlib.util.module_from_spec(spec)
sys.modules["_audited_import"] = module
spec.loader.exec_module(module)
except BaseException as exc: # report, do not grade, the error
error = "%s: %s" % (type(exc).__name__, exc)
json.dump({"target": TARGET, "events": events, "import_error": error},
sys.stdout)
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
sys.exit(main())