test(issue-5): the G9 no-frozen-helper guard reads code, not comments or docstrings
The full suite on1667fdashowed 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
1667fda8dd
commit
4805fd4511
1 file changed
+72
-11
@@ -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) == []
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
Reference in new issue
Block a user