From f711e77849a293a0524ac287672eb86176e825ed Mon Sep 17 00:00:00 2001 From: linmeng <819991304@qq.com> Date: Mon, 27 Jul 2026 21:23:29 +0800 Subject: [PATCH] fix: isolate Gitea Python dependencies Run Python tooling in repository virtual environments so xiaoxin respects the PEP 668 system package boundary. --- .gitea/workflows/backend-quality-gate.yml | 17 ++++---- .gitea/workflows/ci.yml | 14 ++++--- .gitea/workflows/publish-pypi.yml | 14 +++++-- .gitea/workflows/release-quality-gate.yml | 10 +++-- .gitea/workflows/test.yml | 10 +++-- docs/BUG_HISTORY.md | 16 +++++++ .../tests/staging-backend-workflows.test.ts | 42 +++++++++++++++++++ 7 files changed, 98 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/backend-quality-gate.yml b/.gitea/workflows/backend-quality-gate.yml index a925a632..c52328d7 100644 --- a/.gitea/workflows/backend-quality-gate.yml +++ b/.gitea/workflows/backend-quality-gate.yml @@ -45,8 +45,10 @@ jobs: - name: Install dependencies run: | set -euo pipefail - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt -r requirements-dev.txt + python3 -m venv .venv + export PATH="$PWD/.venv/bin:$PATH" + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -r requirements-dev.txt npm ci --prefix frontend - name: Validate backend, package, frontend, and database contracts env: @@ -54,11 +56,12 @@ jobs: NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-placeholder run: | set -euo pipefail - ruff check scripts/run_quality_gate.py tests/test_varga_bphs.py tests/test_ashtakavarga_invariants.py tests/test_cli_smoke.py tests/test_yoga_rules_integrity.py - python3 -m py_compile scripts/*.py jyotish_vedic/*.py mcp_server.py - python3 scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime - python3 scripts/commercial_privacy_artifact_scan.py --json - python3 -m build + export PATH="$PWD/.venv/bin:$PATH" + .venv/bin/ruff check scripts/run_quality_gate.py tests/test_varga_bphs.py tests/test_ashtakavarga_invariants.py tests/test_cli_smoke.py tests/test_yoga_rules_integrity.py + .venv/bin/python -m py_compile scripts/*.py jyotish_vedic/*.py mcp_server.py + .venv/bin/python scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime + .venv/bin/python scripts/commercial_privacy_artifact_scan.py --json + .venv/bin/python -m build npm test --prefix frontend npm run lint --prefix frontend npm run build --prefix frontend diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index e309f03b..dc13e1b1 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -31,14 +31,16 @@ jobs: NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-placeholder run: | set -euo pipefail - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt -r requirements-dev.txt + python3 -m venv .venv + export PATH="$PWD/.venv/bin:$PATH" + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -r requirements-dev.txt npm ci --prefix frontend ruff check scripts/run_quality_gate.py tests/test_varga_bphs.py tests/test_ashtakavarga_invariants.py tests/test_cli_smoke.py tests/test_yoga_rules_integrity.py - python3 -m py_compile scripts/*.py jyotish_vedic/*.py mcp_server.py - python3 scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime - python3 scripts/commercial_privacy_artifact_scan.py --json + python -m py_compile scripts/*.py jyotish_vedic/*.py mcp_server.py + python scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime + python scripts/commercial_privacy_artifact_scan.py --json npm test --prefix frontend npm run lint --prefix frontend npm run build --prefix frontend - python3 -m build + python -m build diff --git a/.gitea/workflows/publish-pypi.yml b/.gitea/workflows/publish-pypi.yml index 2d866cf5..f127b587 100644 --- a/.gitea/workflows/publish-pypi.yml +++ b/.gitea/workflows/publish-pypi.yml @@ -27,11 +27,17 @@ jobs: - name: Build and check package run: | set -euo pipefail - python3 -m pip install build twine - python3 -m build - python3 -m twine check dist/* + python3 -m venv .venv + export PATH="$PWD/.venv/bin:$PATH" + python -m pip install --upgrade pip + python -m pip install build twine + python -m build + python -m twine check dist/* - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: python3 -m twine upload --skip-existing dist/* + run: | + set -euo pipefail + export PATH="$PWD/.venv/bin:$PATH" + python -m twine upload --skip-existing dist/* diff --git a/.gitea/workflows/release-quality-gate.yml b/.gitea/workflows/release-quality-gate.yml index 2ac47f70..b235097f 100644 --- a/.gitea/workflows/release-quality-gate.yml +++ b/.gitea/workflows/release-quality-gate.yml @@ -31,8 +31,10 @@ jobs: NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-placeholder run: | set -euo pipefail - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt -r requirements-dev.txt playwright - python3 -m playwright install --with-deps chromium + python3 -m venv .venv + export PATH="$PWD/.venv/bin:$PATH" + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -r requirements-dev.txt playwright + python -m playwright install --with-deps chromium npm ci --prefix frontend - python3 scripts/run_quality_gate.py --profile release + python scripts/run_quality_gate.py --profile release diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index b64cabc8..9e9c3dfd 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -30,11 +30,13 @@ jobs: NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-placeholder run: | set -euo pipefail - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt -r requirements-dev.txt + python3 -m venv .venv + export PATH="$PWD/.venv/bin:$PATH" + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -r requirements-dev.txt npm ci --prefix frontend - python3 -m pytest -vv --maxfail=1 - python3 tests/run_all.py + python -m pytest -vv --maxfail=1 + python tests/run_all.py npm test --prefix frontend npm run lint --prefix frontend npm run build --prefix frontend diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index d28cb005..4412b400 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -1519,3 +1519,19 @@ - 相关记录:无 - 复发自:无 - 修复版本:待提交(本地可测) + +## BUG-083 | Gitea xiaoxin Runner 向系统 Python 安装依赖触发 PEP 668 + +- 状态:resolved +- 首次发现:2026-07-27 +- 最近更新:2026-07-27 +- 影响面:Gitea 后端质量门禁、CI、完整测试、发布质量门禁与 PyPI 发布 +- 用户现象:xiaoxin Runner 执行 `python3 -m pip install` 时以 `externally-managed-environment` 失败,工作流无法进入后续 Ruff、pytest、Playwright、构建或上传阶段。 +- 触发条件:基于启用 PEP 668 的系统 Python 运行任一包含 pip 安装的 `.gitea/workflows/*.yml`。 +- 根因:BUG-082 统一 Gitea Runner 与原生工具链时只审计了 runner、checkout、触发器和镜像主链,没有约束 Python 依赖隔离;五个工作流仍直接向 Runner 的系统 Python 执行 pip 安装,且 Gitea step 之间不会自动延续 shell 内的 PATH 修改。 +- 修复:所有包含 pip 安装的 Gitea workflow 都先创建仓库内 `.venv` 并在同一安装 step 显式将其 `bin` 目录置于 PATH;后续独立 Python step(包括后端验证与 PyPI 上传)再次显式设置同一 PATH。未使用 `--break-system-packages`,也未修改 GitHub workflows。 +- 验证:前端 Gitea workflow 契约测试枚举全部 `.gitea/workflows/*.yml`,确认五个 pip workflow 均创建并选择 `.venv`、后续 Python step 重新选择 `.venv`、不存在裸 `pip install` 或 `--break-system-packages`;全部 Gitea YAML 完成解析验证。 +- 防复发:工作流审计必须动态枚举全部 Gitea YAML;任何出现 pip install 的 workflow 都必须创建 `.venv`,任何随后执行 Python、Ruff、pytest、Playwright 或构建/上传命令的独立 step 都必须显式导出 venv PATH。 +- 相关记录:BUG-082 +- 复发自:BUG-082 +- 修复版本:待提交(本地可测) diff --git a/frontend/tests/staging-backend-workflows.test.ts b/frontend/tests/staging-backend-workflows.test.ts index f8b311ed..eba88c38 100644 --- a/frontend/tests/staging-backend-workflows.test.ts +++ b/frontend/tests/staging-backend-workflows.test.ts @@ -420,6 +420,48 @@ test("all Gitea workflows use xiaoxin, native checkout, and safe triggers", () = assert.doesNotMatch(all, /GITEA_REGISTRY_USERNAME|GITEA_REGISTRY_TOKEN|git\.copse\.top\/root\/jyotisha-(?:api|web)/); }); +test("Gitea workflows isolate pip installs and Python tooling in a virtualenv", () => { + const names = readdirSync(giteaWorkflowDirectory) + .filter((name) => name.endsWith(".yml")); + const pipInstall = /(?:python3?\s+-m\s+)?pip3?\s+install/; + const pythonTooling = /^\s+(?:python3?|ruff|pytest|playwright|build)(?:\s|$)/m; + const expected = [ + "backend-quality-gate.yml", + "ci.yml", + "publish-pypi.yml", + "release-quality-gate.yml", + "test.yml", + ]; + const workflowsWithPip: string[] = []; + + for (const name of names) { + const workflow = read(new URL(name, giteaWorkflowDirectory)); + if (!pipInstall.test(workflow)) continue; + workflowsWithPip.push(name); + assert.doesNotMatch(workflow, /--break-system-packages/, name); + assert.doesNotMatch(workflow, /^\s+pip3?\s+install/m, name); + + const runBlocks = workflow.match(/^\s{8}run:\s*(?:\|\s*\n(?:\s{10}.*(?:\n|$))+|[^\n]+)$/gm) ?? []; + let virtualenvCreated = false; + for (const block of runBlocks) { + const installsWithPip = pipInstall.test(block); + if (installsWithPip) { + assert.match(block, /python3 -m venv \.venv/, `${name}: pip step must create .venv`); + virtualenvCreated = true; + } + if (virtualenvCreated && pythonTooling.test(block)) { + assert.match( + block, + /export PATH="\$PWD\/\.venv\/bin:\$PATH"/, + `${name}: every later Python step must select .venv`, + ); + } + } + } + + assert.deepEqual(workflowsWithPip.sort(), expected); +}); + test("Gitea staging push validates once then publishes and deploys immutable ACR images", () => { const workflow = read(giteaQualityWorkflow); assert.match(workflow, /publish-and-deploy:[\s\S]*needs: validate/);