From 432ba06c7ae09c5cceb2596e7ee0b46d64b539e1 Mon Sep 17 00:00:00 2001 From: simbiot-harness Date: Mon, 19 Oct 2026 00:03:34 +0000 Subject: [PATCH 1/2] =?UTF-8?q?requirements.txt:=20=D1=80=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D1=8B=D0=BF=D0=B0=D0=BB=D0=B0=D1=81=D1=8C=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=B0=20watchdog=3D=3D6.0.0numpy>=3D2.0=20(?= =?UTF-8?q?=D0=BD=D0=B5=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8)=20=E2=80=94=20pi?= =?UTF-8?q?p=20install=20-r=20=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B2=D0=B5=D0=B6=D0=B8=D1=85=20=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=D1=85=20install.sh;?= =?UTF-8?q?=20numpy=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B9=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 +- tests/test_requirements_valid.py | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/test_requirements_valid.py diff --git a/requirements.txt b/requirements.txt index 9c5aa5d..b21729a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,5 @@ uvicorn==0.54.0 sse-starlette==3.5.0 python-multipart==0.0.32 py-spy==0.4.2 -watchdog==6.0.0numpy>=2.0 # ядро Коди (harness/kodi_core.py): обучение учеников +watchdog==6.0.0 +numpy>=2.0 # ядро Коди (harness/kodi_core.py): обучение учеников diff --git a/tests/test_requirements_valid.py b/tests/test_requirements_valid.py new file mode 100644 index 0000000..579293c --- /dev/null +++ b/tests/test_requirements_valid.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +"""requirements.txt валиден для pip (t-261019001038-0eb7d7) — hermetic. + +Запуск: ./venv/bin/python tests/test_requirements_valid.py + +Регрессия: на origin/master строка watchdog==6.0.0numpy>=2.0 была склеена +без перевода строки — pip install -r requirements.txt падал на СВЕЖИХ +установках install.sh (curl install.sh | bash) с InvalidRequirement, +ломая канал дистрибуции харнеса. Проверяем через packaging.Requirement +(тот же парсер, что у pip): каждая строка файла — ровно одно валидное +требование, watchdog и numpy распознаны отдельными пакетами. +""" +import os +import sys + +sys.path.insert(0, "/root/simbiot-harness") + +from packaging.requirements import Requirement + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +REQ = os.path.join(REPO, "requirements.txt") + +fails = [] + + +def check(name, cond, extra=""): + print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "") + if not cond: + fails.append(name) + + +# все строки-требования парсятся packaging (тот же парсер, что у pip): +# склейка watchdog==6.0.0numpy>=2.0 дала бы InvalidRequirement +req_names = [] +for i, line in enumerate(open(REQ, encoding="utf-8"), 1): + s = line.split("#", 1)[0].strip() + if not s: + continue + try: + r = Requirement(s) + except Exception as e: + check("строка %d валидна (InvalidRequirement)" % i, False, repr(e)) + continue + req_names.append(r.name.lower()) +check("все строки requirements.txt валидны для pip", True) + +# ключевые пакеты распознаны как отдельные требования +for name in ("watchdog", "numpy"): + check("зависимость %r отдельной строкой" % name, name in req_names) + +# битая склейка действительно ловится парсером (self-check теста) +try: + Requirement("watchdog==6.0.0numpy>=2.0") + glued_raises = False +except Exception: + glued_raises = True +check("парсер отклоняет склейку watchdog==6.0.0numpy>=2.0", glued_raises) + +print() +if fails: + print("FAILED:", len(fails)) + sys.exit(1) +print("OK: requirements.txt валиден (watchdog и numpy отдельными строками)") From d3c442a47660861efcb2e04861a9b2c2fab43ac3 Mon Sep 17 00:00:00 2001 From: simbiot Date: Mon, 19 Oct 2026 01:06:53 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test=5Fportal=5Fmcp:=20=D0=B3=D0=B5=D1=80?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=B8=D1=87=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=BF=D1=83?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B8=D0=B7=D0=BE=D0=BB=D1=8F=D1=82=D0=B0=20(?= =?UTF-8?q?=D0=BF=D0=B0=D1=82=D1=87=20=5FHOME/IS=5FISOLATED)=20=E2=80=94?= =?UTF-8?q?=20=D0=BD=D0=B0=20legacy-=D1=85=D0=BE=D1=81=D1=82=D0=B5=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20SIMBIOT=5FHOME=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=20=D0=BB=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE;=20found=20by=20t-261019001038-a355e3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_portal_mcp.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_portal_mcp.py b/tests/test_portal_mcp.py index 6bb3228..80bab95 100644 --- a/tests/test_portal_mcp.py +++ b/tests/test_portal_mcp.py @@ -166,9 +166,18 @@ os.environ["SIMBIOT_PORTAL_MCP_TOKEN_FILE"] = "/tmp/my-token" check("paths: env TOKEN_FILE бьёт схему", _paths.portal_mcp_token_path() == "/tmp/my-token") del os.environ["SIMBIOT_PORTAL_MCP_TOKEN_FILE"] -check("paths: изолят → secrets/portal-mcp-token", - _paths.portal_mcp_token_path().endswith("secrets/portal-mcp-token"), - _paths.portal_mcp_token_path()) +# герметичность: поведение изолята не зависит от того, на каком инстансе +# запущен тест (legacy-хост без SIMBIOT_HOME иначе даёт ложный FAIL) — +# патчим _HOME/IS_ISOLATED как это делают остальные тесты схемы путей +_saved = (_paths._HOME, _paths.IS_ISOLATED) +_paths._HOME = "/tmp/simbiot-test-home" +_paths.IS_ISOLATED = True +try: + check("paths: изолят → secrets/portal-mcp-token", + _paths.portal_mcp_token_path().endswith("secrets/portal-mcp-token"), + _paths.portal_mcp_token_path()) +finally: + _paths._HOME, _paths.IS_ISOLATED = _saved print() print("FAILURES:", fails if fails else "нет — все проверки пройдены")