From 4805fd45113df85f7f696741340f5afae78a36ef Mon Sep 17 00:00:00 2001 From: Godopu Date: Tue, 22 Sep 2026 15:32:40 +0000 Subject: [PATCH] test(issue-5): the G9 no-frozen-helper guard reads code, not comments or docstrings The full suite on 1667fda showed exactly one failure beyond this host's baseline: test_g9_phase2a_spawns_no_daemon_and_calls_no_frozen_helper. Its `\blib\.sh\b` pattern matched a docstring in watcher.py that points at the shell twin of caller_server ("Same convention as lib.sh's _herdr_caller_server") - a mention written so the two implementations stay in sync, not a call. The guard's own docstring states its claim: no frozen lib.sh helper is *invoked* from lib_py/loop. Its one prior change (803ba43) added the word boundary after `hashlib.sha256` tripped it - the same class of false positive, handled by narrowing the matcher rather than by contorting innocent code. This continues that: the three patterns and the word boundary are unchanged, but they now run over the source with comments and docstrings blanked. Every way Python could actually reach a lib.sh helper - a path or `source` in a subprocess argv, an identifier - lives in code text, which is still scanned in full, so the guard keeps its reach. test_g9b pins both sides: an invocation-shaped string in code still trips it (lib.sh and ensure_subscriber_daemon both reported), a bare lib.sh path in a list literal still trips it, and the same words in a module docstring, a function docstring and a comment do not - with hashlib.sha256 present as the original regression. Rephrasing the docstring to dodge the literal was the other option and was not taken: it would hide a useful cross-reference to satisfy a matcher, which is the evasion shape this guard should not encourage, and it would leave the next honest comment to trip the same wire. Co-Authored-By: Claude Fable 5.1 --- tests/test_issue5_phase2a_loop.py | 83 +++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/tests/test_issue5_phase2a_loop.py b/tests/test_issue5_phase2a_loop.py index 1837849..f868031 100644 --- a/tests/test_issue5_phase2a_loop.py +++ b/tests/test_issue5_phase2a_loop.py @@ -692,22 +692,83 @@ def test_g9_loop_package_imports_only_delegation_among_siblings(): assert not offenders, offenders +# The G9 guard reads CODE, not prose. Its claim is that no frozen lib.sh +# helper is *invoked* from lib_py/loop; a comment or docstring that names +# lib.sh (say, to point at the shell twin of a function so the two stay in +# sync) invokes nothing. Every way Python could actually reach a lib.sh helper +# - a path or `source` inside a subprocess argv, an identifier - lives in code +# text, which is still scanned in full, so the guard loses none of its reach. +# History: it first fired on `hashlib.sha256` (a word boundary fixed that), +# then on a docstring cross-reference in watcher.py; both were mentions. +_G9_PATTERNS = { + "ensure_subscriber_daemon": r"ensure_subscriber_daemon", + "preexec_fn": r"preexec_fn", + "lib.sh": r"\blib\.sh\b", # word boundary: `hashlib.sha256` is not lib.sh +} + + +def _code_only(src): + """`src` with comments and docstrings blanked, line structure kept.""" + import ast + import io + import tokenize + + lines = src.splitlines(True) + blank = set() + tree = ast.parse(src) + for node in ast.walk(tree): + if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + body = getattr(node, "body", None) or [] + first = body[0] if body else None + if (isinstance(first, ast.Expr) and isinstance(first.value, ast.Constant) + and isinstance(first.value.value, str)): + blank.update(range(first.lineno, first.end_lineno + 1)) + out = [] + for tok in tokenize.generate_tokens(io.StringIO(src).readline): + if tok.type == tokenize.COMMENT: + (r, c), (_, c2) = tok.start, tok.end + lines[r - 1] = lines[r - 1][:c] + " " * (c2 - c) + lines[r - 1][c2:] + for i, line in enumerate(lines, 1): + out.append("\n" if i in blank else line) + return "".join(out) + + +def _g9_offenders(src): + import re + code = _code_only(src) + return [name for name, pat in _G9_PATTERNS.items() if re.search(pat, code)] + + def test_g9_phase2a_spawns_no_daemon_and_calls_no_frozen_helper(): """The two phase 1 handoffs stay dormant: preexec_fn is unreachable because nothing here spawns, and no frozen lib.sh helper is invoked.""" - import re - - # `lib.sh` needs a word boundary: a bare substring search also matches - # hashlib.sha256, which is how this guard first fired on innocent code. - patterns = { - "ensure_subscriber_daemon": re.compile(r"ensure_subscriber_daemon"), - "preexec_fn": re.compile(r"preexec_fn"), - "lib.sh": re.compile(r"\blib\.sh\b"), - } for path in sorted((SKILLS / "lib_py" / "loop").glob("*.py")): src = path.read_text(encoding="utf-8") - for name, pattern in patterns.items(): - assert not pattern.search(src), "{} references {}".format(path.name, name) + hits = _g9_offenders(src) + assert not hits, "{} references {}".format(path.name, ", ".join(hits)) + + +def test_g9b_the_guard_still_catches_an_invocation_and_ignores_prose(): + """The narrowing must not cost the guard its purpose: an invocation-shaped + reference in code text still trips it; the same words in a docstring or a + comment do not.""" + invoked = ( + 'import subprocess\n' + 'def go():\n' + ' return subprocess.run(["bash", "-c", "source .agents/skills/lib.sh; ensure_subscriber_daemon x"])\n' + ) + assert set(_g9_offenders(invoked)) == {"lib.sh", "ensure_subscriber_daemon"} + path_only = 'CMD = ["bash", "/x/.agents/skills/lib.sh"]\n' + assert _g9_offenders(path_only) == ["lib.sh"] + prose = ( + '"""Module doc: same convention as lib.sh\'s _herdr_caller_server."""\n' + 'import hashlib\n' + 'def f():\n' + ' """See lib.sh; never call ensure_subscriber_daemon from here."""\n' + ' # preexec_fn is documented in lib.sh\n' + ' return hashlib.sha256(b"x").hexdigest()\n' + ) + assert _g9_offenders(prose) == [] # ==============================================================================