Merge remote-tracking branch 'origin/main' into ethie/pm-clean

# Conflicts:
#	hermes_cli/update_cmd.py
#	tests/hermes_cli/test_cmd_update.py
This commit is contained in:
ethernet
2026-09-19 01:14:17 -04:00
132 changed files with 3020 additions and 230 deletions

View File

@@ -49,7 +49,9 @@ def load_patterns(path: Path = PATTERNS) -> list[dict]:
data = json.loads(path.read_text(encoding="utf-8"))
out = []
for p in data["patterns"]:
out.append({**p, "_rx": re.compile(p["pattern_regex"], re.M)})
# ``path_regex`` (optional) restricts a pattern to files whose repo-relative path matches.
path_rx = re.compile(p["path_regex"]) if p.get("path_regex") else None
out.append({**p, "_rx": re.compile(p["pattern_regex"], re.M), "_path_rx": path_rx})
return out
@@ -63,6 +65,9 @@ def scan_text(rel: str, text: str, patterns: list[dict], lines: set[int] | None
findings: list[Finding] = []
src_lines = text.split("\n")
for p in patterns:
path_rx = p.get("_path_rx")
if path_rx is not None and not path_rx.search(rel):
continue
for m in p["_rx"].finditer(text):
line_no = text.count("\n", 0, m.start()) + 1
if lines is not None and line_no not in lines:

View File

@@ -32,7 +32,7 @@
"P24: 62 hits on main",
"P26: 252 hits on main"
],
"usage": "scripts/check_profile_scope_patterns.py --base origin/main [--head HEAD] | --files <paths>"
"usage": "scripts/check_profile_scope_patterns.py --base origin/main [--head HEAD] | --files <paths>; optional path_regex restricts a pattern to matching repo-relative paths"
},
"patterns": [
{
@@ -158,8 +158,16 @@
"id": "P31",
"class": "C6",
"pattern_regex": "f\"agent:\\{|[\"']agent:[\"']\\s*\\+|session_key\\s*=\\s*f\"[a-z]+:",
"scope_hint": "Every adapter-built session key carries the agent:<profile>: namespace (profile 'main' is 'agent:main~'); yuanbao still builds keys with no profile component per the MindDragon probe.",
"scope_hint": "Every adapter-built session key carries the agent:<profile>: namespace (profile 'main' is 'agent:main~'); adapters derive keys through _source_session_key / _event_session_key (P32), never a hand-built prefix.",
"why": "Rows for a served profile land in the root store; browser/computer_use caches never saw the namespace because turns pass the bare session id."
},
{
"id": "P32",
"class": "C4",
"path_regex": "^(gateway|plugins)/platforms/(?!base\\.py$).+\\.py$",
"pattern_regex": "\\bbuild_session_key\\(|\\bSessionSource\\(",
"scope_hint": "Inside an adapter derive every key through self._source_session_key(source) / self._event_session_key(event) (owner-profile namespace, runner-seeded isolation flags) and build sources with self.build_source(...) so the transport provenance is kept; only platforms/base.py owns the free calls.",
"why": "Yuanbao keyed its per-group queue and RecallGuard with the free build_session_key() (no profile) while handle_message keyed under agent:<owner>: - two derivations of one identity, one lane shared across bots (#88715)."
}
]
}

View File

@@ -525,6 +525,12 @@ def _run_one_file_once(
# (venv without pytest, -k that matches nothing) can't report green.
rc = 0
summary = _parse_pytest_summary(output)
crash = _describe_interpreter_crash(rc, output) if rc != 0 else None
if crash:
# Same convention as the timeout path: the diagnosis leads the
# captured output, so the failure dump reads correctly on its own.
summary["crashed"] = 1
output = f"(interpreter crashed: {crash})\n{output}"
subproc_wall = time.monotonic() - subproc_start
return file, rc, output, summary, subproc_wall
@@ -562,6 +568,35 @@ def _parse_pytest_summary(output: str) -> dict[str, int]:
return result
def _describe_interpreter_crash(rc: int, output: str) -> Optional[str]:
"""Return a one-line description when the pytest subprocess died instead of exiting.
A native fault (sqlite stepping a connection another thread closed,
#113186) kills the interpreter mid-file: faulthandler prints ``Fatal
Python error: Segmentation fault`` and the process dies by signal, so
there is no summary line and every count parses to 0. Without this the
file is reported as "no tests ran (collection/import error)" under a
summary that says ``0 failed`` — the wrong diagnosis in both places.
"""
fatal = next(
(line.strip() for line in output.splitlines() if "Fatal Python error:" in line),
None,
)
if fatal:
# The faulthandler banner is appended to the progress dots of the
# last test; keep only the banner.
fatal = fatal[fatal.index("Fatal Python error:"):]
if rc < 0:
import signal as _signal
try:
name = _signal.Signals(-rc).name
except ValueError:
name = f"signal {-rc}"
return f"{fatal} ({name})" if fatal else f"killed by {name}"
return fatal
def _format_file(file: Path, repo_root: Path) -> str:
"""Render a test-file path for display: strip the repo-root prefix
when possible so output reads ``tests/acp_adapter/test_auth.py`` instead of
@@ -624,6 +659,8 @@ def _print_progress(
parts.append(f"{xf}xf")
if xp:
parts.append(f"{xp}xp")
if file_summary.get("crashed"):
parts.append("CRASHED")
test_str = " ".join(parts) + ", " if parts else ""
else:
n_tests = test_counts.get(file, 0)
@@ -1168,11 +1205,12 @@ def main() -> int:
# nothing-ran guard, whereas a file that died before collection reports
# nothing at all and must.
tests_collected = 0
files_crashed = 0
lock = threading.Lock()
def _on_done(file: Path, started_at: float, fut: "Future[Tuple[Path, int, str, Dict[str, int], float]]") -> None:
nonlocal files_done, tests_done, pass_count, fail_count, tests_passed, tests_failed, tests_skipped
nonlocal tests_collected
nonlocal tests_collected, files_crashed
n_tests = test_counts.get(file, 0)
try:
fpath, rc, output, summary, subproc_wall = fut.result()
@@ -1197,6 +1235,7 @@ def main() -> int:
tests_passed += summary.get("passed", 0)
tests_failed += summary.get("failed", 0)
tests_skipped += summary.get("skipped", 0)
files_crashed += summary.get("crashed", 0)
tests_collected += sum(
summary.get(k, 0)
for k in ("passed", "failed", "skipped", "errors", "xfailed", "xpassed")
@@ -1245,7 +1284,13 @@ def main() -> int:
print()
pct = min(100, (tests_done / approx_total_tests * 100)) if approx_total_tests else 0
skipped_note = f", {tests_skipped} skipped" if tests_skipped else ""
print(f"=== Summary: {len(files)} files, {tests_passed} tests passed, {tests_failed} failed{skipped_note} ({pct:.0f}% complete) in {elapsed:.1f}s ({args.jobs} workers) ===")
# A crashed interpreter has no failed-test count; say so on the one line
# everyone reads, or "0 failed" + exit 1 looks like a runner bug.
crashed_note = (
f", {files_crashed} file{'s' if files_crashed != 1 else ''} CRASHED"
if files_crashed else ""
)
print(f"=== Summary: {len(files)} files, {tests_passed} tests passed, {tests_failed} failed{crashed_note}{skipped_note} ({pct:.0f}% complete) in {elapsed:.1f}s ({args.jobs} workers) ===")
# Host-OS gating note: tests marked for another OS were skipped by the
# conftest hook, not run. Say so explicitly — a green local run on Linux
@@ -1268,7 +1313,7 @@ def main() -> int:
# The summary line above reads green at a glance ("0 failed ... 100%
# complete"), which has been misread as a successful verification, so say
# it plainly AND fail the exit code.
no_tests_ran_at_all = bool(files) and tests_collected == 0
no_tests_ran_at_all = bool(files) and tests_collected == 0 and not files_crashed
if no_tests_ran_at_all:
print()
print(
@@ -1336,11 +1381,17 @@ def main() -> int:
print(output.rstrip())
print()
# Split: files with actual test failures vs non-zero exit for other reasons
test_fail_files = [(f, s) for f, _o, s in failures if s.get("failed", 0) > 0]
all_passed_but_nonzero = [(f, s) for f, _o, s in failures
crashed_files = [(f, o, s) for f, o, s in failures if s.get("crashed")]
rest = [(f, s) for f, _o, s in failures if not s.get("crashed")]
test_fail_files = [(f, s) for f, s in rest if s.get("failed", 0) > 0]
all_passed_but_nonzero = [(f, s) for f, s in rest
if s.get("failed", 0) == 0 and s.get("passed", 0) > 0]
no_tests_ran = [(f, s) for f, _o, s in failures
no_tests_ran = [(f, s) for f, s in rest
if s.get("failed", 0) == 0 and s.get("passed", 0) == 0]
if crashed_files:
print(f"=== {len(crashed_files)} file{'s' if len(crashed_files) != 1 else ''} where the interpreter CRASHED mid-run (native fault — a real bug, not a collection error; the tests that did run are not counted) ===")
for file, output, _s in crashed_files:
print(f" {_format_file(file, repo_root)} {output.splitlines()[0]}")
if test_fail_files:
total_tf = sum(s.get("failed", 0) for _, s in test_fail_files)
print(f"=== {len(test_fail_files)} file{'s' if len(test_fail_files) != 1 else ''} with test failures ({total_tf} test{'s' if total_tf != 1 else ''} failed) ===")