Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/nsc/eval/gate.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 覆盖)。"""

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确认问题真实,但修复涉及资产层真相与 CI 流程的所有权决策,超出本卡(SW-04 的范围=去除 gate_enabled() 的 env 覆盖)能自行决定的面:\n\n1. judge-calibration.yml(状态文件)按 ADR-0006/D28 属"真相在 git"的候选——把它提交进库等于把校准结果变成资产层,需要 ADR + 人类确认;\n2. 或者改 workflow(judge-calibration.yml 用 artifact/cache 持久化状态、llm-eval.yml 读取)——CI 层改动也已在 PR 描述"遗留"一节记录为待 owner 决策;\n3. 本 PR 已把惰性化的 gh variable set 动作与 env 注入记录在案(不再被代码读取)。\n\n已在 PR 描述补充"遗留"清单。建议:如果 owner 同意"校准状态文件入库",我可以另开一张卡(提交 judge-calibration.yml + workflow 改为读文件 + 删除 gh variable 动作),一张卡做完不扩散本 PR。请裁决。

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))
Expand Down Expand Up @@ -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 {})
Expand Down
28 changes: 21 additions & 7 deletions tests/test_judge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading