fix(#23, #24): 교차 서버 herdr 스코프와 에이전트 무관 시작 다이얼로그, 실제 에이전트가 충족하는 온보딩 전달 판정 #35

Merged
Godopu merged 15 commits from fix/issue-23-24 into main 2026-09-23 23:36:37 +00:00
Showing only changes of commit 4805fd4511 - Show all commits
+72 -11
View File
@@ -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) == []
# ==============================================================================