From 8493d47090202c0d8be718b435a1304cb112b726 Mon Sep 17 00:00:00 2001 From: randypanding Date: Sat, 22 Aug 2026 04:22:24 +0800 Subject: [PATCH] =?UTF-8?q?SW-04=20gate=5Fenabled()=20=E5=8E=BB=E9=99=A4?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E8=A6=86=E7=9B=96=EF=BC=9A?= =?UTF-8?q?=E9=97=A8=E7=A6=81=E7=9C=9F=E7=9B=B8=E5=8F=AA=E5=9C=A8=E6=A0=A1?= =?UTF-8?q?=E5=87=86=E7=8A=B6=E6=80=81=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gate_enabled() 不再读 JUDGE_GATE_ENABLED(原 优先级 env > 状态文件 > 默认); D8:未过校准门槛的判官只能出报告,env 开关等于给未校准判官留后门 - 删除随之死亡的 gate_var_name();CLI 无状态文件回退改为默认开启 - 测试:test_gate_enabled_respects_env 断言的是本卡移除的 env 覆盖语义, 按卡面替换为 test_gate_enabled_ignores_env / defaults_on_without_state (规格与既有测试冲突,已按工单裁决记录于 PR 描述) --- src/nsc/eval/gate.py | 22 ++++++++-------------- tests/test_judge.py | 28 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/nsc/eval/gate.py b/src/nsc/eval/gate.py index 8b38cbc..0509afc 100644 --- a/src/nsc/eval/gate.py +++ b/src/nsc/eval/gate.py @@ -1,12 +1,13 @@ -"""评测门禁(T-08b):阈值加载 + JUDGE_GATE_ENABLED + 校准结果判定。 +"""评测门禁(T-08b):阈值加载 + 校准结果判定。 D8:未过校准门槛的判官只能出报告,不能参与门禁。 -`judge calibrate` 未过闸时会把仓库变量 JUDGE_GATE_ENABLED 写为 false(协议 §4)。 +`judge calibrate` 未过闸时会把 judge-calibration.yml 的 judge_gate_enabled 写为 false +(协议 §4)。SW-04:门禁真相只在状态文件,环境变量不再有覆盖能力—— +环境开关等于给"未校准判官可门禁"留后门,与 D8 相悖。 """ from __future__ import annotations -import os from pathlib import Path from typing import Any @@ -20,15 +21,8 @@ def load_thresholds(path: str | Path = THRESHOLDS_PATH) -> dict[str, Any]: return yaml.safe_load(Path(path).read_text("utf-8")) or {} -def gate_var_name() -> str: - return str(load_thresholds().get("l1", {}).get("judge_gate_enabled_var", "JUDGE_GATE_ENABLED")) - - def gate_enabled() -> bool: - """判官是否允许参与门禁。优先级:环境变量 > judge-calibration.yml > 默认开启。""" - val = os.environ.get(gate_var_name()) - if val is not None: - return val.strip().lower() not in ("0", "false", "off", "no", "") + """判官是否允许参与门禁。真相:judge-calibration.yml > 默认开启(SW-04:无 env 覆盖)。""" if GATE_STATE_PATH.exists(): data = yaml.safe_load(GATE_STATE_PATH.read_text("utf-8")) or {} return bool(data.get("judge_gate_enabled", True)) @@ -108,10 +102,10 @@ def main(argv: list[str] | None = None) -> int: ev = evaluate_calibration(metrics) else: if not GATE_STATE_PATH.exists(): - # CI 场景(judge-calibration.yml 尚未提交):回退到 gate_enabled() 的 - # 环境变量(JUDGE_GATE_ENABLED)/ 默认开启,避免硬失败。 + # CI 场景(judge-calibration.yml 尚未提交):回退到默认开启,避免硬失败。 + # SW-04:环境变量不再有覆盖能力(D8:未校准判官只能出报告)。 enabled = gate_enabled() - print(f"无校准状态文件;JUDGE_GATE_ENABLED={str(enabled).lower()}") + print(f"无校准状态文件;gate_enabled={str(enabled).lower()}") return 0 if enabled else 1 state = yaml.safe_load(GATE_STATE_PATH.read_text("utf-8")) or {} ev = evaluate_calibration(state.get("metrics") or {}) diff --git a/tests/test_judge.py b/tests/test_judge.py index 5a58a61..df7bee3 100644 --- a/tests/test_judge.py +++ b/tests/test_judge.py @@ -227,16 +227,30 @@ def test_compute_metrics(): # ---------------------------------------------------------------- 门禁 -def test_gate_enabled_respects_env(monkeypatch): +def test_gate_enabled_ignores_env(tmp_path, monkeypatch): + """SW-04:环境变量不得覆盖校准门禁。D8 真相只在 judge-calibration.yml。 + + 取代旧 test_gate_enabled_respects_env(其断言的 env 覆盖语义正是本卡移除对象, + 冲突记录见 PR 描述)。 + """ from nsc.eval import gate as g - monkeypatch.setenv("JUDGE_GATE_ENABLED", "0") - assert g.gate_enabled() is False + state = tmp_path / "state.yml" + monkeypatch.setattr(g, "GATE_STATE_PATH", state) monkeypatch.setenv("JUDGE_GATE_ENABLED", "true") - assert g.gate_enabled() is True - monkeypatch.delenv("JUDGE_GATE_ENABLED") - monkeypatch.setattr(g, "GATE_STATE_PATH", Path("/nonexistent/state.yml")) - assert g.gate_enabled() is True # 默认开 + state.write_text("judge_gate_enabled: false\n", "utf-8") + assert g.gate_enabled() is False, "env=true 不得越过校准关闸" + monkeypatch.setenv("JUDGE_GATE_ENABLED", "0") + state.write_text("judge_gate_enabled: true\n", "utf-8") + assert g.gate_enabled() is True, "env=0 不得关掉已校准的门禁" + + +def test_gate_enabled_defaults_on_without_state(tmp_path, monkeypatch): + from nsc.eval import gate as g + + monkeypatch.setattr(g, "GATE_STATE_PATH", tmp_path / "nonexistent.yml") + monkeypatch.setenv("JUDGE_GATE_ENABLED", "0") + assert g.gate_enabled() is True # 无校准状态文件:默认开 def test_gate_state_file_fallback(tmp_path, monkeypatch):