From 7ba5bfaf8190a42e4d405f0a2f67839bfd7ad37f Mon Sep 17 00:00:00 2001 From: host Date: Sat, 31 Oct 2026 05:13:29 +0000 Subject: [PATCH] ci2: fix worker F821 NameError, mark harness2 sha1 non-security, rename E741 var - worker._tenant_dirs: playwright refresh log used undefined 'task'; use the method argument project_id (ruff F821, real bug: NameError aborted tenant seeding when a playwright script was refreshed). Regression: cloud/tests/test_worker_playwright_seed_f821.py (2 tests; pre-fix run reproduces NameError at worker.py:2780). - harness2.assign_ids: hashlib.sha1(..., usedforsecurity=False) -- the digest is only a deterministic shuffle seed, not a security use; digest value unchanged, bandit B324 no longer fires (High 5 -> 4). - harness2.select_final: rename ambiguous loop var 'l' to 'lab' (ruff E741, behaviour-invariant). - Deliberately NOT changed: host.py shell=True x4 (see PR body -- all four are intentional shell commands, changing semantics is unsafe), remaining ruff 109 / mypy 24, workflow thresholds (75% coverage gate unchanged; no gate was relaxed or removed). --- cloud/harness2.py | 8 +- .../tests/test_worker_playwright_seed_f821.py | 83 +++++++++++++++++++ cloud/worker.py | 6 +- 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 cloud/tests/test_worker_playwright_seed_f821.py diff --git a/cloud/harness2.py b/cloud/harness2.py index f74c67f..357d31b 100644 --- a/cloud/harness2.py +++ b/cloud/harness2.py @@ -1209,7 +1209,10 @@ def _anon_id(index: int) -> str: def assign_ids(labels: Sequence[str], *, task_id: str, k: int, mode: str) -> dict[str, str]: """匿名 id -> 真实 label:按排序后定种子洗牌,同输入永远同结果。""" - seed = int(hashlib.sha1(f"{task_id}|{k}|{mode}".encode()).hexdigest()[:12], 16) + # 仅确定性洗牌种子,非安全用途(不防碰撞、不做完整性校验): + # usedforsecurity=False 后 bandit B324 不再误报,且摘要值不变。 + seed = int(hashlib.sha1(f"{task_id}|{k}|{mode}".encode(), + usedforsecurity=False).hexdigest()[:12], 16) ordered = sorted(labels) random.Random(seed).shuffle(ordered) return {_anon_id(i): lbl for i, lbl in enumerate(ordered)} @@ -1284,7 +1287,8 @@ def select_final( except Exception as exc: # 回落:已交付的终版里排第一的那个;连这个都没有才算选失败。 sel.chosen_label = sorted(pool)[0] - sel.chosen_id = next(a for a, l in sel.id_map.items() if l == sel.chosen_label) + sel.chosen_id = next(a for a, lab in sel.id_map.items() + if lab == sel.chosen_label) sel.fallback = True sel.error = str(exc)[:400] sel.ok = True diff --git a/cloud/tests/test_worker_playwright_seed_f821.py b/cloud/tests/test_worker_playwright_seed_f821.py new file mode 100644 index 0000000..8e2f0bd --- /dev/null +++ b/cloud/tests/test_worker_playwright_seed_f821.py @@ -0,0 +1,83 @@ +"""Regression for ruff F821 in Worker._tenant_dirs (worker.py:2780). + +The playwright seeding branch logged +``task.get("project_id")`` even though ``_tenant_dirs`` only receives +``project_id`` -- there is no ``task`` variable in scope. The branch is +only reached when playwright seeding is on AND the seeder reports a +refreshed script, so ordinary seeding tests (flag off, or no refresh) +never hit it: at runtime the log call raised NameError and aborted +tenant seeding for an existing project whose playwright scripts had +just been refreshed. + +No Docker, no network: _tenant_dirs runs against temp dirs and the +playwright seeder is patched to report a refresh, so the test proves +only that the log branch uses the method argument, not that real +scripts are copied. +""" + +import os +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +try: + from cloud.server import init_db + from cloud.worker import Worker +except ImportError: + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from cloud.server import init_db + from cloud.worker import Worker + + +class WorkerPlaywrightSeedF821TestCase(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.db_path = os.path.join(self.tmp.name, "control.db") + init_db(self.db_path) + self.template_home = os.path.join(self.tmp.name, "tpl-home") + os.makedirs(self.template_home) + open(os.path.join(self.template_home, "config.toml"), + "w").write('[model_provider.xcmapi]\n' + 'base_url="https://xcmapi.org/v1"\n') + self.template_repo = os.path.join(self.tmp.name, "tpl-repo") + os.makedirs(os.path.join(self.template_repo, ".git")) + open(os.path.join(self.template_repo, "sample.py"), + "w").write("def add(a, b):\n return a + b\n") + + def tearDown(self): + self.tmp.cleanup() + + def _worker(self): + return Worker( + self.db_path, "/fake/host.py", + env={"XCM_API_KEY": "test-placeholder"}, + tenants_root=os.path.join(self.tmp.name, "tenants"), + tenant_template_home=self.template_home, + tenant_template_repo=self.template_repo, + tenant_isolation=True, + playwright_mcp=True) + + def test_refreshed_script_logs_project_id_without_name_error(self): + # Pre-fix this raised NameError: name 'task' is not defined. + worker = self._worker() + with mock.patch("cloud.worker._ensure_playwright_mcp", + return_value={"script_refreshed": + ["playwright_mcp.py"]}): + with self.assertLogs(worker.logger, level="INFO") as logs: + dirs = worker._tenant_dirs("projA") + self.assertIn("codex_home", dirs) + self.assertTrue(any("projA" in line and "playwright_mcp.py" in line + for line in logs.output)) + + def test_no_refresh_still_seeds(self): + worker = self._worker() + with mock.patch("cloud.worker._ensure_playwright_mcp", + return_value={"script_refreshed": []}): + dirs = worker._tenant_dirs("projA") + self.assertIn("codex_home", dirs) + + +if __name__ == "__main__": + unittest.main() diff --git a/cloud/worker.py b/cloud/worker.py index 59b79e5..993405a 100644 --- a/cloud/worker.py +++ b/cloud/worker.py @@ -2776,9 +2776,13 @@ class Worker: "/var/lib/codex-app-server/codex-home/" "playwright_mcp.py"]) if _seeded.get("script_refreshed"): + # _tenant_dirs 只收 project_id 参数,作用域里没有 task: + # 此前写 task.get("project_id") 在脚本刷新时抛 NameError, + # 中断整个租户播种(ruff F821,回归见 + # test_worker_playwright_seed_f821)。 self.logger.info( "playwright MCP scripts refreshed for project %s: %s", - task.get("project_id"), + project_id, ",".join(_seeded["script_refreshed"])) if _seeded.get("script_skipped_modified"): self.logger.warning(