Skip to content

feat(verifier): 入职考试 CI+校准回流+rubric shadow(W5-C3 .github#226,ADR-0072) - #64

Merged
randypanding merged 2 commits into
mainfrom
w5c3-verifier-exam
Aug 21, 2026
Merged

feat(verifier): 入职考试 CI+校准回流+rubric shadow(W5-C3 .github#226,ADR-0072)#64
randypanding merged 2 commits into
mainfrom
w5c3-verifier-exam

Conversation

@randypanding

Copy link
Copy Markdown
Contributor

动机

宪法 §4C 持证上岗制 / ADR-0072:verifier 入职考试(CI 关卡)+ 校准集循环 +
AI 可读性 rubric shadow 的操作面。范式署名:LLM-as-a-Verifier(arXiv:2607.05391)、
RewardBench2/LLMBar(考试集形态)、MT-Bench 位置交换(arXiv:2306.05685)、
null-model 攻击实证(arXiv:2410.07137)、敏感度/特异度校准(arXiv:2511.21140,ICML'26)。
考试集冻结正本在 archive(配套 PR:archive w5c3-evalsets);执照面在 agent-registry
(配套 PR:agent-registry w5c3-registry)。

变更清单

  • pipeline/verifier-exam/run_exam.py:考试 runner——冻结校验 fail-closed(哈希不符=exit 2
    不许开考);四分项门(rb2 ≥0.70 / llmbar ≥0.60 / 金丝雀 negative_rate=1.00 / 双序 ≥0.90,
    阈值真源 exam-policy.yaml);任一不过=exit 1 拒上岗;成绩按
    judge_id@exam_version@prompt_hash12 存档(JSONL,同键追加留史);replay(零真实 LLM)
    /api(经 scripts/llm-call.sh 计量唯一入口)判官适配;输入隔离(黄金标签永不进判官通道)。
  • pipeline/verifier-exam/calibrate.py:校准回流(AC-3)——owner merge/reject GitHub 事件
    →校准样本(dedup 幂等,零额外操作);出分带校正值±Wilson CI;CI 下界<及格线→
    needs_human 信号(不阻断,exit 0)
    ;owner 确认率 ≥20% 指标位(从 0 起累积)。
  • pipeline/verifier-exam/rubric_shadow.py + rubrics/ai-readability-v1.yaml(AC-4):
    五维 rubric(可定位性/入口清晰度/模块深度/命名词表/示例新鲜度)判据分解+每档锚定描述;
    仅记录不阻断;标注负债显式申报(annotation_debt 字段)。
  • pipeline/verifier-exam/examset/v1/(钉版副本,与 archive 正本字节一致)+
    exam-pin.yaml(freeze_hash 供应链锚)+ prompts/v1 + judge-configs + fixtures。
  • pipeline/verifier-exam/tests/:31 例(零真实 LLM)。
  • .github/workflows/verifier-exam.yml:selftest / 考试(无凭据=回放模式)+ 拒上岗门语义
    演示(位置偏差判官必须被拒)/ 成绩存档断言 / 每日 freeze drift 对账(archive 正本 vs pin)/
    每日校准回流 / rubric shadow(全 job 顶层最小权限+timeout+concurrency,actions SHA 钉版)。

AC 映射(.github#226)

  • AC-1 Given 新 judge/模型候选(judge-configs/*.json:模型别名+prompt 版本+采样参数
    全锁定)When verifier-exam workflow 运行 Then 考试集全过才可注册(执照注册校验在
    agent-registry 配套 PR,replay 成绩被拒);任一分项不过 exit 1;成绩按
    judge_id@版本@prompt_hash 存档(workflow verifier-exam-results artifact,键断言步骤)。
  • AC-2 Given 金丝雀样本 When 考试 Then 100% 判负——test_every_real_canary_judged_negative
    逐条断言 24/24;单点翻转(judge-canary-miss fixture)→ negative_rate=23/24 → exit 1。
  • AC-3 Given owner merge/reject 动作 When 发生(每日 cron calibrate.py collect 消费
    GitHub PR 事件)Then 自动成为校准样本(dedup_key 幂等,零额外操作);出分=校正值±Wilson CI;
    test_boundary_ci_lower_equals_pass_line_not_escalated 钉死"CI 下界严格低于及格线才升人类"。
  • AC-4 Given 五维 rubric When shadow 运行 Then 出分仅记录不阻断
    test_all_zero_scores_still_exit_zero:全零极端分仍 exit 0 + blocking=false);
    标注负债有申报记录(annotation_debt:数据不足维度显式 insufficient-data+reason)。

测试方法(本地已跑,零网络零真实推理)

  • python3 -m unittest discover -s pipeline/verifier-exam/tests -v31 例全绿
    金丝雀 24/24 判负、双序边界(gate 函数 0.89 拒/0.90 过 + 100 条集成 0.90 过/0.89 拒)、
    位置偏差判官 agreement=0.0 被拒、冻结篡改 exit 2、存档键完整性(采样参数变同键/
    prompt 变新键)、Wilson 已知值 (0.7,10)→(0.3965,0.8920)、rubric 不阻断+负债申报。
  • CLI 端到端:run_exam.py(gold 轨道 exit 0:四分项 1.0/1.0/1.0/1.0;
    positional 轨道 exit 1:dual_order=0.0);workflow 全 run 块 bash -n 通过、YAML 可解析。

风险与回滚

  • 风险:api 模式真实判官未经实跑(本卡零真实 LLM 纪律)→ dispatch(api) 首跑即暴露,
    llm-call.sh 失败=分项计错(fail-closed);校准样本暂存 artifact(retention 90 天)→
    容量上来后转仓库存档(follow-up)。
  • 回滚:删除 workflow + pipeline/verifier-exam/ 即回"无 T2 执法"世界;T1 确定性关卡
    不受影响(ADR-0072 回滚条款);rubric shadow 本身零阻断。

Card: Cloudbird-Software/.github#226
ADR: ADR-0072(archive adr/ADR-0072-verifier-entrance-exam-calibration.md

pipeline/verifier-exam/:run_exam.py(冻结校验 fail-closed+四分项门+成绩按
judge_id@版本@prompt_hash 存档+replay/api 判官适配)+calibrate.py(owner
merge/reject 事件→校准样本静默回流+敏感度/特异度 Wilson CI+下界不及格自动
升人类信号)+rubric_shadow.py(五维 AI 可读性 rubric,仅记录不阻断+负债申报)
+钉版考试集(freeze_hash 锚 exam-pin.yaml)+31 例自测;workflow verifier-exam
(selftest/考试/拒上岗门语义演示/每日 drift 对账/校准回流/rubric shadow)。
Copilot AI lite review requested due to automatic review settings August 21, 2026 20:18
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@randypanding, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 seconds

Limit details: You’ve used all 10 included reviews currently available.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b31744a6-8915-4fcd-9987-4d7640fd643a

📥 Commits

Reviewing files that changed from the base of the PR and between ee07743 and b5d5d3f.

📒 Files selected for processing (22)
  • .github/workflows/verifier-exam.yml
  • pipeline/verifier-exam/calibrate.py
  • pipeline/verifier-exam/exam-pin.yaml
  • pipeline/verifier-exam/exam-policy.yaml
  • pipeline/verifier-exam/examset/v1/llmbar-adversarial.jsonl
  • pipeline/verifier-exam/examset/v1/manifest.json
  • pipeline/verifier-exam/examset/v1/null-canaries.jsonl
  • pipeline/verifier-exam/examset/v1/rewardbench2-generative.jsonl
  • pipeline/verifier-exam/fixtures/judge-canary-miss.json
  • pipeline/verifier-exam/fixtures/judge-gold.json
  • pipeline/verifier-exam/fixtures/judge-positional.json
  • pipeline/verifier-exam/fixtures/rubric-scores-ci.json
  • pipeline/verifier-exam/judge-configs/glm-4.5-air.json
  • pipeline/verifier-exam/prompts/v1/canary-judge.md
  • pipeline/verifier-exam/prompts/v1/pairwise-judge.md
  • pipeline/verifier-exam/rubric_shadow.py
  • pipeline/verifier-exam/rubrics/ai-readability-v1.yaml
  • pipeline/verifier-exam/run_exam.py
  • pipeline/verifier-exam/tests/examutil.py
  • pipeline/verifier-exam/tests/test_calibrate.py
  • pipeline/verifier-exam/tests/test_exam.py
  • pipeline/verifier-exam/tests/test_rubric_shadow.py

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add verifier entrance exam CI with calibration feedback and rubric shadow

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add CI-gated verifier entrance exam with frozen examset integrity checks and fail-closed behavior.
• Add calibration feedback loop from owner merge/reject events with Wilson CI and escalation signal.
• Add AI-readability rubric shadow scoring pipeline that records results without blocking.
Diagram

graph TD
  WF(["verifier-exam workflow"]) --> INPUTS["Exam inputs (examset/policy/prompts)" ] --> RUN(["run_exam.py"]) --> ART[("CI artifacts")]
  WF --> CAL(["calibrate.py"]) --> GH{{"GitHub PR events"}} --> ART
  WF --> SPEC["Rubric spec" ] --> RS(["rubric_shadow.py"]) --> ART

  subgraph Legend
    direction LR
    _p(["Process"]) ~~~ _f["File/Data"] ~~~ _e{{"External"}} ~~~ _a[("Artifact")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt an existing eval harness (e.g., lm-eval-style runner)
  • ➕ Leverages mature dataset loading, metrics, and reporting conventions
  • ➕ Potentially easier to extend to more task types and metrics
  • ➖ Harder to enforce the PR’s strict supply-chain freeze semantics (byte-level pin + fail-closed)
  • ➖ May complicate the “no gold labels enter judge channel” constraint and replay/api dual-mode
2. Keep examset only in archive and fetch at runtime (no vendored copy)
  • ➕ Single source of truth for exam bytes; avoids duplication in CI-Workflows repo
  • ➕ Simplifies updating pinned versions (just bump pin)
  • ➖ Introduces network/repo availability dependency into gating workflow
  • ➖ Makes local/offline selftests harder; undermines “zero network” unit test posture

Recommendation: The PR’s approach (vendored pinned examset + fail-closed verification + replay-first tests) is the best fit for CI gating with strong integrity guarantees and offline determinism. The main tradeoff (duplicated exam bytes) is justified by the requirement to prevent drift and to keep unit tests networkless.

Files changed (22) +2018 / -0

Enhancement (8) +902 / -0
calibrate.pyAdd calibration backflow collection and Wilson CI scoring +235/-0

Add calibration backflow collection and Wilson CI scoring

• Implements AC-3: collects owner merge/reject ground truth from GitHub PR events into deduplicated JSONL samples and computes calibrated scores using sensitivity/specificity with Wilson 95% confidence intervals. Emits a non-blocking escalation signal (needs_human) when CI lower bound falls below the pass line; fail-closed on collection errors.

pipeline/verifier-exam/calibrate.py

llmbar-adversarial.jsonlAdd LLMBar-aligned adversarial pairwise subset (v1) +20/-0

Add LLMBar-aligned adversarial pairwise subset (v1)

• Adds 20 adversarial pairwise items (obvious/subtle distractor forms) used to evaluate robustness against style-over-substance answers. This is part of the pinned, frozen examset v1.

pipeline/verifier-exam/examset/v1/llmbar-adversarial.jsonl

null-canaries.jsonlAdd null-model canary set requiring 100% negative verdicts +24/-0

Add null-model canary set requiring 100% negative verdicts

• Adds 24 canary items (empty response, template parrot, refusal) that must be judged negative to pass. Serves as a hard gate to detect null-model/cheap-judge vulnerabilities.

pipeline/verifier-exam/examset/v1/null-canaries.jsonl

rewardbench2-generative.jsonlAdd RewardBench2-aligned generative pairwise subset (v1) +20/-0

Add RewardBench2-aligned generative pairwise subset (v1)

• Adds 20 pairwise items across math/code/editing/summarization/knowledge/instruction-following categories aligned to RewardBench2 generative format. Items include gold labels/rationales for maintainers but are intended to be isolated from judge inputs.

pipeline/verifier-exam/examset/v1/rewardbench2-generative.jsonl

canary-judge.mdAdd canary judge prompt with strict verdict protocol +26/-0

Add canary judge prompt with strict verdict protocol

• Defines the instruction prompt for single-response canary judging, enforcing that empty/parrot/refusal responses are negative. Specifies a strict output protocol (VERDICT line) for robust parsing.

pipeline/verifier-exam/prompts/v1/canary-judge.md

pairwise-judge.mdAdd pairwise preference judging prompt with adversarial guidance +39/-0

Add pairwise preference judging prompt with adversarial guidance

• Defines the pairwise judging prompt and ranking criteria, explicitly warning about style-over-substance distractors and delimiting content as non-instructions. Enforces a strict, non-tie verdict protocol for parsing.

pipeline/verifier-exam/prompts/v1/pairwise-judge.md

rubric_shadow.pyAdd AI-readability rubric shadow scorer with annotation debt recording +199/-0

Add AI-readability rubric shadow scorer with annotation debt recording

• Implements AC-4 shadow scoring: extracts deterministic repo context, accepts replay scores (or future API scoring), and writes JSONL records with per-dimension scores and explicit annotation_debt entries when data is insufficient. Guarantees non-blocking behavior (exit 0) except for configuration errors (exit 2).

pipeline/verifier-exam/rubric_shadow.py

run_exam.pyAdd entrance exam runner with freeze verification, gates, and result archiving +339/-0

Add entrance exam runner with freeze verification, gates, and result archiving

• Implements the verifier entrance exam with fail-closed examset integrity checks, replay/API judge adapters, strict verdict parsing, and enforced input isolation (gold labels never sent to judge). Computes section metrics (RB2, LLMBar, canary negative rate, dual-order agreement), applies policy-defined thresholds, and archives results keyed by judge_id@exam_version@prompt_hash12.

pipeline/verifier-exam/run_exam.py

Tests (8) +723 / -0
judge-canary-miss.jsonAdd replay fixture that fails the canary hard gate +72/-0

Add replay fixture that fails the canary hard gate

• Provides a replay judge decision table that intentionally marks one canary as positive, validating that the canary negative_rate gate rejects the judge (exit 1). Used to prove AC-2 behavior in tests.

pipeline/verifier-exam/fixtures/judge-canary-miss.json

judge-gold.jsonAdd gold replay fixture for a fully passing judge +72/-0

Add gold replay fixture for a fully passing judge

• Adds a deterministic replay fixture that matches all gold pairwise labels and marks all canaries negative. Used for offline CI runs and baseline pass-path assertions.

pipeline/verifier-exam/fixtures/judge-gold.json

judge-positional.jsonAdd positional-bias replay fixture to demonstrate dual-order rejection +6/-0

Add positional-bias replay fixture to demonstrate dual-order rejection

• Defines a replay judge mode that always picks the first-presented response, producing near-zero dual-order agreement. Used to assert the gating semantics that positional bias is rejected (exit 1).

pipeline/verifier-exam/fixtures/judge-positional.json

rubric-scores-ci.jsonAdd rubric shadow replay scores fixture (with missing dimension) +8/-0

Add rubric shadow replay scores fixture (with missing dimension)

• Adds a fixture providing placeholder continuous scores for four dimensions and intentionally omitting example_freshness. Drives validation that missing data results in explicit annotation debt rather than silent scoring.

pipeline/verifier-exam/fixtures/rubric-scores-ci.json

examutil.pyAdd shared test utilities for synthetic exams, fixtures, and runner invocation +106/-0

Add shared test utilities for synthetic exams, fixtures, and runner invocation

• Provides helpers to synthesize examsets with matching manifests, generate gold fixtures (with controlled flips), and run run_exam.main while capturing output. Simplifies deterministic unit tests for gate boundaries and archive key behavior.

pipeline/verifier-exam/tests/examutil.py

test_calibrate.pyAdd calibration unit tests for collection, Wilson CI, and escalation rules +142/-0

Add calibration unit tests for collection, Wilson CI, and escalation rules

• Adds offline tests validating PR event classification into approve/reject/ambiguous, deduplication idempotency, Wilson CI correctness, and needs_human boundary behavior (strictly lower than pass line). Also asserts the CLI remains non-blocking (exit 0) even when escalating.

pipeline/verifier-exam/tests/test_calibrate.py

test_exam.pyAdd exam runner unit/integration tests for freeze, canaries, gates, and archiving +203/-0

Add exam runner unit/integration tests for freeze, canaries, gates, and archiving

• Adds comprehensive offline tests for freeze verification (tamper → exit 2), canary 24/24 negative enforcement, dual-order 0.89/0.90 boundary behavior, positional-bias rejection, verdict parsing, and archive_key semantics including prompt-hash changes and history appends.

pipeline/verifier-exam/tests/test_exam.py

test_rubric_shadow.pyAdd rubric shadow tests for non-blocking behavior and debt reporting +114/-0

Add rubric shadow tests for non-blocking behavior and debt reporting

• Validates rubric contract completeness (5 dimensions × 5 anchors), ensures shadow scoring never blocks even for all-zero scores, and asserts missing/out-of-range dimension data becomes explicit annotation debt. Includes deterministic context probing for README-claimed path resolution.

pipeline/verifier-exam/tests/test_rubric_shadow.py

Other (6) +393 / -0
verifier-exam.ymlAdd verifier-exam workflow with gating, drift check, calibration, and rubric shadow +242/-0

Add verifier-exam workflow with gating, drift check, calibration, and rubric shadow

• Introduces a multi-job GitHub Actions workflow that runs unit selftests, executes the entrance exam (replay by default, API optionally), asserts archiving semantics, and demonstrates rejection behavior. Adds scheduled jobs for examset drift verification against archive, calibration sample collection/scoring, and rubric shadow recording, all with pinned action SHAs and minimal permissions.

.github/workflows/verifier-exam.yml

exam-pin.yamlPin upstream examset version and freeze hash for supply-chain anchoring +14/-0

Pin upstream examset version and freeze hash for supply-chain anchoring

• Defines the pinned upstream archive path/version and the expected freeze_hash used for drift detection. Documents the policy that examset changes must occur via new version directories, not in-place edits.

pipeline/verifier-exam/exam-pin.yaml

exam-policy.yamlDefine exam gating thresholds and calibration defaults as policy-of-record +25/-0

Define exam gating thresholds and calibration defaults as policy-of-record

• Adds the single source of truth for per-section thresholds (RewardBench2, LLMBar, null canary, dual-order agreement) and calibration settings (min joined pairs, pass line). Ensures run_exam consumes thresholds from policy instead of hardcoding.

pipeline/verifier-exam/exam-policy.yaml

manifest.jsonAdd examset manifest with per-file hashes and freeze_hash +42/-0

Add examset manifest with per-file hashes and freeze_hash

• Introduces the manifest schema describing composition, per-file sha256, and the aggregate freeze_hash used for fail-closed integrity verification. Also documents the results archive key format used by downstream licensing checks.

pipeline/verifier-exam/examset/v1/manifest.json

glm-4.5-air.jsonAdd example judge configuration with locked sampling parameters +13/-0

Add example judge configuration with locked sampling parameters

• Introduces a sample judge config defining judge_id/model alias, prompt version, and deterministic sampling parameters (temperature=0, seed, etc.). Serves as the config-of-record for exam execution and for result archival metadata.

pipeline/verifier-exam/judge-configs/glm-4.5-air.json

ai-readability-v1.yamlAdd AI-readability rubric contract (5 dimensions with anchored descriptions) +57/-0

Add AI-readability rubric contract (5 dimensions with anchored descriptions)

• Defines the rubric schema, five dimensions, and five anchor levels per dimension for continuous scoring. Documents the annotation debt policy and the rule that rubric shadow must not block CI.

pipeline/verifier-exam/rubrics/ai-readability-v1.yaml

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

该 PR 为 verifier 入职考试体系引入完整的 CI 关卡实现:包含冻结考试集校验的考试 runner、基于 GitHub PR owner 行为的校准回流与 Wilson CI 计算、以及 AI 可读性 rubric 的 shadow 记录管道,并配套单元测试与 GitHub Actions 工作流来演示/执行“拒上岗门”语义。

Changes:

  • 新增 run_exam.py:支持考试集冻结校验(fail-closed)、回放/真实判官两种模式、四分项门阈值判定与成绩存档键。
  • 新增 calibrate.py:从 PR merge/reject 事件静默回流校准样本,并输出校正分与 Wilson CI(needs_human 仅信号不阻断)。
  • 新增 rubric_shadow.py + rubric/prompt/fixtures/workflow:五维 rubric shadow 出分与负债申报(仅记录不阻断),并通过 workflow 定时产出 artifacts。

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pipeline/verifier-exam/run_exam.py 考试 runner:冻结校验、分项评测、门禁判定、成绩落档与 replay/api 判官适配
pipeline/verifier-exam/calibrate.py 校准回流与出分校准:owner 行为采样、Wilson CI、needs_human 信号输出
pipeline/verifier-exam/rubric_shadow.py AI 可读性 rubric shadow:上下文提取、回放/(预留)api 打分、负债申报与记录落盘
pipeline/verifier-exam/rubrics/ai-readability-v1.yaml 五维 rubric 契约与锚档描述(含 annotation_debt_policy)
pipeline/verifier-exam/exam-policy.yaml 考试门阈值真源(四分项指标与校准参数默认值)
pipeline/verifier-exam/exam-pin.yaml 供应链钉版:archive 正本 freeze_hash 锚与 drift 检查依据
pipeline/verifier-exam/examset/v1/manifest.json 考试集冻结 manifest(逐文件 sha256 + freeze_hash)
pipeline/verifier-exam/examset/v1/rewardbench2-generative.jsonl RewardBench2 形态的成对偏好题集(冻结副本)
pipeline/verifier-exam/examset/v1/llmbar-adversarial.jsonl LLMBar 形态的对抗成对偏好题集(冻结副本)
pipeline/verifier-exam/examset/v1/null-canaries.jsonl null-model 金丝雀集合(冻结副本)
pipeline/verifier-exam/prompts/v1/pairwise-judge.md 成对偏好判题 prompt(参与 prompt_hash)
pipeline/verifier-exam/prompts/v1/canary-judge.md 金丝雀判负 prompt(参与 prompt_hash)
pipeline/verifier-exam/judge-configs/glm-4.5-air.json 候选判官示例配置(锁定采样参数并记录)
pipeline/verifier-exam/fixtures/judge-gold.json 回放判官金标准 fixture(用于自测/CI)
pipeline/verifier-exam/fixtures/judge-canary-miss.json 回放 fixture:单点金丝雀翻转用于拒上岗测试
pipeline/verifier-exam/fixtures/judge-positional.json 回放 fixture:位置偏差靶子(双序一致率应为 0)
pipeline/verifier-exam/fixtures/rubric-scores-ci.json rubric shadow 的 CI 回放打分 fixture(含缺维触发负债申报)
pipeline/verifier-exam/tests/examutil.py 测试工具:合成考试集/fixture/运行入口与记录读取
pipeline/verifier-exam/tests/test_exam.py run_exam 关键语义测试:冻结、金丝雀、双序边界、存档键、输入隔离
pipeline/verifier-exam/tests/test_calibrate.py calibrate 关键语义测试:Wilson、去重回流、needs_human 边界与非阻断
pipeline/verifier-exam/tests/test_rubric_shadow.py rubric shadow 关键语义测试:契约完整性、负债申报、不阻断
.github/workflows/verifier-exam.yml CI 工作流:selftest、考试(replay/api)、drift 对账、校准回流、rubric shadow artifacts
Suppressed comments (1)

.github/workflows/verifier-exam.yml:97

  • 同上,门语义演示步骤也固定使用 judge-configs/glm-4.5-air.json;如果 workflow_dispatch 选择了其他 model_alias,演示与主考试会出现不一致。建议这里同样使用 inputs.model_alias 选择 judge-config。
          python3 pipeline/verifier-exam/run_exam.py \
            --judge-config pipeline/verifier-exam/judge-configs/glm-4.5-air.json \
            --replay-fixture pipeline/verifier-exam/fixtures/judge-positional.json \
            --out "$RUNNER_TEMP/rejected" --run-id gate-demo > /dev/null 2>&1

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +254 to +263
def gate(sections: dict, policy: dict) -> bool:
"""任一分项 value < min → False(拒上岗)。阈值真源=policy,不硬编码。"""
for name, spec in policy.get("sections", {}).items():
if name not in sections:
raise ExamError(f"policy 声明的分项缺结果: {name}")
metric = METRIC_KEY[spec["metric"]]
sections[name]["metric"] = metric
sections[name]["threshold"] = spec["min"]
sections[name]["pass"] = sections[name][metric] >= spec["min"]
return all(sections[n]["pass"] for n in policy.get("sections", {}))
Comment on lines +187 to +195
def _call(self, user_text: str) -> str:
pf = self.tmp / "verifier-exam-prompt.txt"
pf.write_text(user_text, encoding="utf-8")
r = subprocess.run(self.base_args + ["--prompt-file", str(pf), "--tag", self.tag],
capture_output=True, text=True, timeout=180)
if r.returncode != 0:
return "" # 调用失败=不可解析=计错(不中断整场考试,逐条记录)
return r.stdout

Comment on lines +123 to +133
pf = Path("rubric-prompt.txt")
pf.write_text(prompt, encoding="utf-8")
s = judge_cfg.get("sampling", {})
args = ["bash", str(Path(repo_root) / "scripts" / "llm-call.sh"),
"--model", judge_cfg["model_alias"], "--prompt-file", str(pf),
"--tag", f"rubric-shadow@{judge_cfg.get('judge_id', 'unknown')}"]
if s.get("temperature") is not None:
args += ["--temperature", str(s["temperature"])]
r = subprocess.run(args, capture_output=True, text=True, timeout=180)
if r.returncode != 0:
raise RubricError(f"llm-call.sh 失败 rc={r.returncode}: {r.stderr[:200]}")
Comment on lines +105 to +107
args = ["gh", "api", f"repos/{repo}/pulls",
"-f", "state=closed", "-f", "sort=updated", "-f", "direction=desc",
"-f", "per_page=30"]
Comment on lines +77 to +80
python3 pipeline/verifier-exam/run_exam.py \
--judge-config pipeline/verifier-exam/judge-configs/glm-4.5-air.json \
--judge-mode "$JUDGE_MODE" \
--out verifier-exam/results --run-id "$RUN_ID" > "$RUNNER_TEMP/exam.json"
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (14) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Freeze anchor never enforced 🐞 Bug ⛨ Security
Description
load_and_verify() validates the vendored files only against their adjacent, PR-editable manifest
and never checks exam-pin.yaml, so changing the exam files and recomputing the manifest is
accepted without creating a new version. The daily drift job also compares only the upstream
manifest metadata to the pin, not the vendored or upstream file bytes, so it cannot detect this
replacement.
Code

pipeline/verifier-exam/run_exam.py[R69-72]

+    manifest = json.loads(mpath.read_text(encoding="utf-8"))
+    files, freeze = compute_freeze(exam_dir)
+    for name, info in manifest.get("files", {}).items():
+        if name not in files:
Relevance

●●● Strong

Runner bypasses its own stated supply-chain anchor, letting PR-controlled manifest recomputation
replace frozen exam files.

PR-#8

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The runner recomputes files and compares them only with manifest.json; no code reads the
separately added pin. The drift workflow reads the upstream manifest and compares its declared
fields to the pin, while exam-pin.yaml declares the pin as the supply-chain anchor for the local
vendor.

pipeline/verifier-exam/run_exam.py[64-83]
pipeline/verifier-exam/exam-pin.yaml[8-14]
.github/workflows/verifier-exam.yml[146-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The runner trusts a manifest that can be changed together with the frozen exam, so the pinned freeze hash is not enforced.

## Issue Context
Load `exam-pin.yaml`, require its version and freeze hash to match the locally recomputed exam, and make drift verification recompute both upstream and vendored file hashes rather than trusting upstream manifest metadata.

## Fix Focus Areas
- pipeline/verifier-exam/run_exam.py[64-83]
- pipeline/verifier-exam/exam-pin.yaml[8-14]
- .github/workflows/verifier-exam.yml[146-160]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Collection sends POST requests 🐞 Bug ≡ Correctness
Description
fetch_events_online() adds -f parameters without --method GET, which makes gh api send POST
requests to read-only pulls and reviews endpoints. Scheduled collection therefore fails before
producing calibration samples.
Code

pipeline/verifier-exam/calibrate.py[R105-107]

+    args = ["gh", "api", f"repos/{repo}/pulls",
+            "-f", "state=closed", "-f", "sort=updated", "-f", "direction=desc",
+            "-f", "per_page=30"]
Relevance

●●● Strong

Adding gh api fields without GET turns read requests into POST, causing collection failure.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both calls add raw fields and omit an explicit method. The GitHub CLI manual states that adding
request parameters automatically switches the method to POST and that --method GET is required to
put them in the query string.

pipeline/verifier-exam/calibrate.py[103-123]
🌐 The default becomes POST when parameters are added; use --method GET to send them as a GET query string.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`gh api -f` changes these collection calls to POST, but both endpoints must be read with GET.

## Issue Context
Add `--method GET` to both pulls and reviews calls while preserving query parameters.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[103-123]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Verdicts never join samples 🐞 Bug ≡ Correctness
Description
Every collected calibration sample is created with judge_verdict: null, and neither the CLI nor
workflow contains a step that fills it. Since score() accepts only positive/negative verdicts,
calibration permanently remains insufficient-calibration and never computes sensitivity or
specificity.
Code

pipeline/verifier-exam/calibrate.py[R80-82]

+    return {"schema": SCHEMA_SAMPLE, "sample_id": "cal-" + hashlib.sha256(dk.encode()).hexdigest()[:10],
+            "dedup_key": dk, "ts": ts, "source": "owner-action", "repo": repo,
+            "pr": pr["number"], "owner_action": action, "judge_verdict": None}
Relevance

●●● Strong

Samples always have null verdicts and no join/enrichment stage exists, so calibration stays
insufficient.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Sample creation always writes a null verdict, scoring filters those records out, and the workflow
invokes only collect followed by score with no enrichment stage.

pipeline/verifier-exam/calibrate.py[76-82]
pipeline/verifier-exam/calibrate.py[135-151]
.github/workflows/verifier-exam.yml[183-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Collected owner actions never receive corresponding judge verdicts, so no calibration pair can be scored.

## Issue Context
Add a deterministic join key and an ingestion/join command or collect the verifier verdict with each event before invoking `score`.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[76-82]
- pipeline/verifier-exam/calibrate.py[135-151]
- .github/workflows/verifier-exam.yml[183-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (6)
4. Calibration uses replay accuracy 🐞 Bug ≡ Correctness
Description
Scheduled runs have no workflow-dispatch inputs, so JUDGE_MODE defaults to replay and the
calibration job receives the gold fixture's exam accuracy rather than a real verifier's score. Even
after joins are implemented, reports would calibrate the replay fixture instead of the deployed
judge.
Code

.github/workflows/verifier-exam.yml[R57-58]

+    env:
+      JUDGE_MODE: ${{ inputs.judge_mode || 'replay' }}   # zizmor:模板值经 env 间接引用
Relevance

●●● Strong

Scheduled runs default to replay, so calibration uses fixture accuracy rather than a deployed judge
score.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The exam mode expression falls back to replay when schedule events have no inputs. Calibration
depends on that job's raw_pairwise_acc output and passes it directly as --raw-score.

.github/workflows/verifier-exam.yml[55-58]
.github/workflows/verifier-exam.yml[162-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The scheduled calibration pipeline always feeds replay accuracy into calibration.

## Issue Context
Source raw score and verdicts from the actual licensed verifier being calibrated, or run a credentialed API exam in a separately protected scheduled job.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[55-58]
- .github/workflows/verifier-exam.yml[162-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Secret reaches PR code 🐞 Bug ⛨ Security
Description
The exam step unconditionally injects LLM_API_KEY while executing run_exam.py from the pull
request checkout, including in default replay mode. A same-repository PR that modifies the runner
can read or exfiltrate the organization secret despite the comment claiming PR-modifiable code
cannot access it.
Code

.github/workflows/verifier-exam.yml[R70-74]

+      - name: 入职考试(任一分项不过=exit 1 拒上岗)
+        id: exam
+        env:
+          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+          RUN_ID: ${{ github.run_id }}   # zizmor:模板值经 env 间接引用
Relevance

●●● Strong

Secret is exposed to PR-controlled code unconditionally; concrete credential-exfiltration risk.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow is triggered by pull requests, checks out their code, places the secret in the
environment, and executes the changed runner. Replay is the default, but the secret injection is not
conditional on API mode.

.github/workflows/verifier-exam.yml[17-25]
.github/workflows/verifier-exam.yml[57-80]
scripts/llm-call.sh[38-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A secret is exposed to code controlled by pull-request changes.

## Issue Context
Do not inject the key in replay/PR jobs. Put credentialed API execution in a protected workflow/job that runs trusted code and is not triggered from an untrusted PR checkout.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[17-25]
- .github/workflows/verifier-exam.yml[60-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Rubric API always fails accounting 🐞 Bug ≡ Correctness
Description
score_api() forwards only temperature and omits max_tokens, but llm-call.sh rejects every
otherwise successful call whose usage record has a null sampling.max_tokens. Thus the advertised
--judge-config API path always raises RubricError after the provider responds.
Code

pipeline/verifier-exam/rubric_shadow.py[R125-131]

+    s = judge_cfg.get("sampling", {})
+    args = ["bash", str(Path(repo_root) / "scripts" / "llm-call.sh"),
+            "--model", judge_cfg["model_alias"], "--prompt-file", str(pf),
+            "--tag", f"rubric-shadow@{judge_cfg.get('judge_id', 'unknown')}"]
+    if s.get("temperature") is not None:
+        args += ["--temperature", str(s["temperature"])]
+    r = subprocess.run(args, capture_output=True, text=True, timeout=180)
Relevance

●●● Strong

Required max_tokens omitted causes deterministic API contract failure; the advertised API path
cannot succeed.

PR-#56

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The rubric path adds only --temperature. The wrapper records null max tokens when omitted and
explicitly requires .sampling.max_tokens != null, exiting 3 on validation failure; the rubric
converts that nonzero exit into RubricError.

pipeline/verifier-exam/rubric_shadow.py[125-133]
scripts/llm-call.sh[79-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Rubric API calls omit a sampling parameter required by the shared LLM wrapper's accounting validation.

## Issue Context
Forward `max_tokens` and the other supported locked parameters from the judge config, using the same adapter behavior as the exam runner.

## Fix Focus Areas
- pipeline/verifier-exam/rubric_shadow.py[125-133]
- scripts/llm-call.sh[79-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Calibration history never accumulates 🐞 Bug ≡ Correctness
Description
Each scheduled calibration run starts from a fresh checkout and creates a new local
calibration/samples.jsonl, because the workflow only uploads the file after scoring and never
restores a prior artifact or other durable store. As a result, append_samples can only
deduplicate/accumulate within a single run, and the seven-day collector window causes older
calibration evidence to be permanently lost over time.
Code

.github/workflows/verifier-exam.yml[R188-195]

+          python3 pipeline/verifier-exam/calibrate.py collect \
+            --repo "$REPO_FULL" --out calibration/samples.jsonl
+      - name: 出分即校准(CI 下界<及格线→needs_human 信号,不阻断)
+        run: |
+          set -euo pipefail
+          python3 pipeline/verifier-exam/calibrate.py score \
+            --samples calibration/samples.jsonl --raw-score "${RAW_ACC:-0.0}" \
+            --out calibration/report.json
Relevance

●●● Strong

Artifacts uploaded but never restored, so each scheduled run loses prior samples and history.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited workflow behavior indicates that every scheduled job runs in a clean workspace, generates
and scores a local calibration/samples.jsonl, and then uploads it as an artifact without any
corresponding artifact download, repository write, or other restore step in subsequent runs. Since
append_samples() can only inspect and append to whatever output file already exists on the runner,
starting from a fresh checkout means it always sees an empty/new file, preventing cross-run
deduplication and accumulation; combined with collection defaulting to a seven-day window, older
samples will inevitably drop out and cannot be recovered in later runs.

.github/workflows/verifier-exam.yml[176-203]
pipeline/verifier-exam/calibrate.py[85-100]
pipeline/verifier-exam/calibrate.py[179-188]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Scheduled calibration runs do not restore prior `calibration/samples.jsonl` data, so each run starts with an empty/new local samples file and `append_samples` only deduplicates/accumulates within that single run. This prevents calibration history from building over time and causes events older than the collector window (e.g., seven days) to be permanently lost.

## Issue Context
The workflow performs a fresh checkout on each scheduled job, writes and scores `calibration/samples.jsonl`, and uploads it as an artifact, but does not download/restore that artifact (or otherwise persist the JSONL) at the start of the next run. Implement a durable storage approach with an explicit restore/update strategy (e.g., repository-backed versioned store or artifact download before collection and upload after scoring) so JSONL history and deduplication persist across runs, and ensure concurrent scheduled executions cannot overwrite each other.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[176-203]
- pipeline/verifier-exam/calibrate.py[85-100]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Selected model is ignored 🐞 Bug ≡ Correctness
Description
The workflow dispatch exposes a model_alias selector, but the exam command always loads the
checked-in fixed GLM config and never uses the input, so dispatching a different candidate still
evaluates glm-4.5-air. This makes API model selection ineffective and can mislead results by
recording the wrong model identity, preventing the intended candidate-model entrance exam workflow.
Code

.github/workflows/verifier-exam.yml[R77-80]

+          python3 pipeline/verifier-exam/run_exam.py \
+            --judge-config pipeline/verifier-exam/judge-configs/glm-4.5-air.json \
+            --judge-mode "$JUDGE_MODE" \
+            --out verifier-exam/results --run-id "$RUN_ID" > "$RUNNER_TEMP/exam.json"
Relevance

●●● Strong

Dispatch model selector is unused while command hardcodes glm-4.5-air, a deterministic correctness
defect.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The dispatch defines model_alias as the only input representing the candidate model, but it is
never interpolated or referenced in the workflow command, which instead hardcodes a single
judge/config path. The run_exam.py path shows the runner constructing ApiJudge using only that
JSON config, meaning both the actual model called and the identity recorded for the run come
exclusively from the fixed config, regardless of the model_alias provided at dispatch time.

.github/workflows/verifier-exam.yml[23-27]
.github/workflows/verifier-exam.yml[77-80]
pipeline/verifier-exam/run_exam.py[169-184]
pipeline/verifier-exam/judge-configs/glm-4.5-air.json[1-10]
.github/workflows/verifier-exam.yml[70-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow-dispatch `model_alias` input is declared but never consumed by the exam execution. As a result, the exam always runs with the static checked-in GLM configuration (and records `glm-4.5-air`), so selecting another candidate model via API has no effect and produces misleading model identity/results.

## Issue Context
Update the workflow and runner so the requested alias meaningfully affects which judge/model configuration is used during the exam. Either (a) resolve `model_alias` to a trusted, allow-listed judge config path/identifier, or (b) generate an ephemeral configuration from the input while keeping fixed prompts/sampling/settings for comparability; alternatively, if dynamic selection is not intended, remove `model_alias` and require explicit config selection.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[23-27]
- .github/workflows/verifier-exam.yml[70-80]
- pipeline/verifier-exam/run_exam.py[169-184]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Top-p setting is not applied 🐞 Bug ≡ Correctness
Description
The judge records top_p as a locked sampling parameter in the config and archives it in results as
if applied, but ApiJudge never forwards it and llm-call.sh has no --top-p option. This can
cause actual API behavior to differ from the sampling configuration claimed in the result,
undermining reproducibility when provider defaults differ from the config.
Code

pipeline/verifier-exam/run_exam.py[R171-181]

+        s = judge_config.get("sampling") or {}
+        self.model = judge_config["model_alias"]
+        self.base_args = ["bash", str(repo_root / "scripts" / "llm-call.sh"), "--model", self.model]
+        if s.get("temperature") is not None:
+            self.base_args += ["--temperature", str(s["temperature"])]
+        if s.get("max_tokens") is not None:
+            self.base_args += ["--max-tokens", str(s["max_tokens"])]
+        if s.get("thinking"):
+            self.base_args += ["--thinking", str(s["thinking"])]
+        if s.get("seed") is not None:
+            self.base_args += ["--seed", str(s["seed"])]
Relevance

●● Moderate

Recorded sampling contract differs from actual provider behavior, but adding top_p support requires
broader wrapper/provider changes.

PR-#56

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The shipped judge configuration includes a top_p field, but the API argument construction in
ApiJudge forwards other sampling parameters (e.g., temperature, max tokens, thinking, seed) and
does not include top_p, and the shared wrapper script llm-call.sh neither parses nor sends a
top-p value because it has no corresponding option. Despite this, the archived exam result retains
the complete config object including top_p, creating a mismatch between the recorded “locked”
sampling settings and what was actually used in the provider request.

pipeline/verifier-exam/judge-configs/glm-4.5-air.json[5-10]
pipeline/verifier-exam/run_exam.py[169-181]
pipeline/verifier-exam/run_exam.py[266-281]
scripts/llm-call.sh[24-57]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`top_p` is accepted in judge configurations and is archived in results as if it were applied, but `ApiJudge` never forwards it to provider calls and `llm-call.sh` does not currently accept or transmit a top-p value, so the recorded sampling configuration may not match actual API behavior.

## Issue Context
Implement consistent `top_p` support end-to-end (validate/accept via the wrapper, include in provider requests, and pass through from `ApiJudge`), and ensure any usage/recording schema reflects what was truly applied; alternatively, if `top_p` is not supported for a given pathway/provider, reject configs containing unsupported sampling fields or avoid recording them as “locked/applied” to prevent false reproducibility claims.

## Fix Focus Areas
- pipeline/verifier-exam/run_exam.py[169-181]
- pipeline/verifier-exam/run_exam.py[266-281]
- pipeline/verifier-exam/judge-configs/glm-4.5-air.json[5-10]
- scripts/llm-call.sh[24-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

10. Collection truncates event history 🐞 Bug ☼ Reliability
Description
Online calibration collection only fetches a single 30-item page of closed PRs and reviews, so
qualifying pulls or later reviews (e.g., a CHANGES_REQUESTED beyond the first page) are silently
dropped and can be misclassified as absent. This yields incomplete, biased calibration data instead
of failing or fully collecting within a busy seven-day window.
Code

pipeline/verifier-exam/calibrate.py[R105-107]

+    args = ["gh", "api", f"repos/{repo}/pulls",
+            "-f", "state=closed", "-f", "sort=updated", "-f", "direction=desc",
+            "-f", "per_page=30"]
Relevance

●●● Strong

Single-page collection can silently omit governed data; PR #8 accepted pagination fix for same
pattern.

PR-#8

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited code issues API requests with per_page=30 and then immediately iterates over the single
decoded response, with no logic to request subsequent pages or otherwise paginate, meaning only the
first page of results is ever processed. Because no additional requests are made after that initial
response, any eligible closed PRs (and associated reviews) beyond the first 30 are excluded,
matching a previously observed missing-pagination pattern in the repository.

pipeline/verifier-exam/calibrate.py[103-123]
pipeline/verifier-exam/calibrate.py[103-124]
PR-#8

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Online calibration collection (via `fetch_events_online`) reads only one GitHub API page (`per_page=30`) for pulls and reviews and never paginates, silently omitting eligible closed PRs and later reviews beyond the first page; this can misclassify review state (e.g., missing a `CHANGES_REQUESTED`) and bias the calibration set.

## Issue Context
Collection is intended to automatically convert all owner merge/reject actions into calibration samples across the full `since` window (e.g., seven days). Update the GitHub API calls to paginate through all pages (e.g., using `gh api --paginate --method GET` or equivalent), correctly combine items across pages, and stop processing once records are older than `since` (so pagination doesn’t over-fetch unnecessarily).

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[103-124]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Review evidence is truncated 🐞 Bug ≡ Correctness
Description
Reject classification examines only the first 30 reviews of an unmerged closed PR. If its
CHANGES_REQUESTED review appears on a later page, the PR is treated as ambiguous and skipped
despite having the required rejection evidence.
Code

pipeline/verifier-exam/calibrate.py[R119-122]

+            rv = subprocess.run(["gh", "api", f"repos/{repo}/pulls/{pr['number']}/reviews",
+                                 "-f", "per_page=30"], capture_output=True, text=True, timeout=60)
+            if rv.returncode == 0:
+                pr["_reviews"] = json.loads(rv.stdout)
Relevance

●●● Strong

Review evidence capped at 30 with no pagination; PR #8 accepted pagination for analogous
governed-data collection.

PR-#8

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Classification requires a CHANGES_REQUESTED state, but the code populates _reviews from one
request explicitly capped at 30 entries.

pipeline/verifier-exam/calibrate.py[66-73]
pipeline/verifier-exam/calibrate.py[117-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Review collection requests only one page of 30 reviews. This can omit a `CHANGES_REQUESTED` review and prevent a closed PR from becoming a reject calibration sample.

## Issue Context
`classify_pr` depends on `_reviews` containing a `CHANGES_REQUESTED` state. Fetch all relevant review pages before classification.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[66-73]
- pipeline/verifier-exam/calibrate.py[117-123]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. API scores exceed rubric range 🐞 Bug ☼ Reliability
Description
The rubric API parser accepts values such as 1.5 even though the rubric scale is [0,1], then
includes them directly in the recorded dimensional mean. A malformed judge response can therefore
produce an impossible shadow score instead of an explicit no-data debt.
Code

pipeline/verifier-exam/rubric_shadow.py[R135-138]

+    for d in DIMENSIONS:
+        m = re.search(rf"^DIM\s+{d}\s+([01](?:\.\d+)?)\s*$", r.stdout, re.M | re.I)
+        dims[d] = {"score": float(m.group(1)) if m else None,
+                   "basis": "api" if m else "unparseable"}
Relevance

●●● Strong

Parser violates the explicitly documented [0,1] rubric contract; range validation is a deterministic
correctness fix.

PR-#56

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The prompt and rubric declare a 0-to-1 scale, but the regex permits any decimal suffix after 1 and
the value is used directly in the mean.

pipeline/verifier-exam/rubric_shadow.py[119-139]
pipeline/verifier-exam/rubric_shadow.py[172-182]
pipeline/verifier-exam/rubrics/ai-readability-v1.yaml[5-7]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The API-mode score regex accepts decimals above one (for example `1.5`) and stores them without validation, violating the rubric's declared `[0,1]` scale.

## Issue Context
Treat any missing, duplicate, malformed, non-finite, or out-of-range dimension score as unparseable/no-data so annotation debt is emitted rather than calculating an invalid mean.

## Fix Focus Areas
- pipeline/verifier-exam/rubric_shadow.py[119-139]
- pipeline/verifier-exam/rubric_shadow.py[166-182]
- pipeline/verifier-exam/rubrics/ai-readability-v1.yaml[5-7]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (2)
13. Review failures become ambiguity 🐞 Bug ☼ Reliability
Description
Failures from the per-PR reviews API are ignored, causing a real rejected PR to be silently treated
as ambiguous and omitted. This contradicts the function's fail-closed contract and biases
calibration whenever GitHub returns a transient error or rate limit.
Code

pipeline/verifier-exam/calibrate.py[R119-122]

+            rv = subprocess.run(["gh", "api", f"repos/{repo}/pulls/{pr['number']}/reviews",
+                                 "-f", "per_page=30"], capture_output=True, text=True, timeout=60)
+            if rv.returncode == 0:
+                pr["_reviews"] = json.loads(rv.stdout)
Relevance

●● Moderate

Ignoring review API failures conflicts with fail-closed intent, but no close accepted precedent
found.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Only successful review calls populate _reviews; nonzero return codes are ignored. classify_pr()
then returns an empty action when _reviews is absent, dropping the sample.

pipeline/verifier-exam/calibrate.py[66-73]
pipeline/verifier-exam/calibrate.py[117-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Review API failures are silently converted into missing review evidence.

## Issue Context
Raise `CalibError` on nonzero review requests and validate returned JSON before classifying the PR.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[117-123]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Freshness uses checkout timestamps 🐞 Bug ≡ Correctness
Description
build_context() derives document lag from filesystem mtimes, but Actions checkout materializes
repository files during the same run, making those timestamps checkout-order artifacts rather than
commit ages. The freshness input is therefore usually zero/unknown and cannot measure whether
examples or documentation lag code.
Code

pipeline/verifier-exam/rubric_shadow.py[R51-54]

+    def newest(patterns):
+        ts = [p.stat().st_mtime for pat in patterns for p in root.rglob(pat)
+              if ".git/" not in p.as_posix()]
+        return max(ts) if ts else None
Relevance

●● Moderate

Checkout-time mtimes are likely unreliable freshness signal, but subjective measurement-design issue
lacking close precedent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The context scanner uses stat().st_mtime for Markdown and code files, immediately after a workflow
checkout. It only emits lag when the resulting code timestamp is later, otherwise marking freshness
unavailable and adding annotation debt.

pipeline/verifier-exam/rubric_shadow.py[51-62]
pipeline/verifier-exam/rubric_shadow.py[94-99]
.github/workflows/verifier-exam.yml[221-234]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Filesystem mtimes after checkout do not represent documentation or code change age.

## Issue Context
Derive timestamps from Git history for relevant tracked files, or use explicit example-to-code metadata instead of checkout mtimes.

## Fix Focus Areas
- pipeline/verifier-exam/rubric_shadow.py[51-62]
- .github/workflows/verifier-exam.yml[221-234]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Web pages:
  +3 more
Review mode: 🧠 Deep: This is a security- and governance-sensitive CI gate with substantial new logic across runner, calibration, rubric, fixtures, and workflows, creating many independent, easy-to-miss failure modes.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +69 to +72
manifest = json.loads(mpath.read_text(encoding="utf-8"))
files, freeze = compute_freeze(exam_dir)
for name, info in manifest.get("files", {}).items():
if name not in files:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Freeze anchor never enforced 🐞 Bug ⛨ Security

load_and_verify() validates the vendored files only against their adjacent, PR-editable manifest
and never checks exam-pin.yaml, so changing the exam files and recomputing the manifest is
accepted without creating a new version. The daily drift job also compares only the upstream
manifest metadata to the pin, not the vendored or upstream file bytes, so it cannot detect this
replacement.
Agent Prompt
## Issue description
The runner trusts a manifest that can be changed together with the frozen exam, so the pinned freeze hash is not enforced.

## Issue Context
Load `exam-pin.yaml`, require its version and freeze hash to match the locally recomputed exam, and make drift verification recompute both upstream and vendored file hashes rather than trusting upstream manifest metadata.

## Fix Focus Areas
- pipeline/verifier-exam/run_exam.py[64-83]
- pipeline/verifier-exam/exam-pin.yaml[8-14]
- .github/workflows/verifier-exam.yml[146-160]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +105 to +107
args = ["gh", "api", f"repos/{repo}/pulls",
"-f", "state=closed", "-f", "sort=updated", "-f", "direction=desc",
"-f", "per_page=30"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Collection sends post requests 🐞 Bug ≡ Correctness

fetch_events_online() adds -f parameters without --method GET, which makes gh api send POST
requests to read-only pulls and reviews endpoints. Scheduled collection therefore fails before
producing calibration samples.
Agent Prompt
## Issue description
`gh api -f` changes these collection calls to POST, but both endpoints must be read with GET.

## Issue Context
Add `--method GET` to both pulls and reviews calls while preserving query parameters.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[103-123]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +80 to +82
return {"schema": SCHEMA_SAMPLE, "sample_id": "cal-" + hashlib.sha256(dk.encode()).hexdigest()[:10],
"dedup_key": dk, "ts": ts, "source": "owner-action", "repo": repo,
"pr": pr["number"], "owner_action": action, "judge_verdict": None}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Verdicts never join samples 🐞 Bug ≡ Correctness

Every collected calibration sample is created with judge_verdict: null, and neither the CLI nor
workflow contains a step that fills it. Since score() accepts only positive/negative verdicts,
calibration permanently remains insufficient-calibration and never computes sensitivity or
specificity.
Agent Prompt
## Issue description
Collected owner actions never receive corresponding judge verdicts, so no calibration pair can be scored.

## Issue Context
Add a deterministic join key and an ingestion/join command or collect the verifier verdict with each event before invoking `score`.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[76-82]
- pipeline/verifier-exam/calibrate.py[135-151]
- .github/workflows/verifier-exam.yml[183-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +57 to +58
env:
JUDGE_MODE: ${{ inputs.judge_mode || 'replay' }} # zizmor:模板值经 env 间接引用

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Calibration uses replay accuracy 🐞 Bug ≡ Correctness

Scheduled runs have no workflow-dispatch inputs, so JUDGE_MODE defaults to replay and the
calibration job receives the gold fixture's exam accuracy rather than a real verifier's score. Even
after joins are implemented, reports would calibrate the replay fixture instead of the deployed
judge.
Agent Prompt
## Issue description
The scheduled calibration pipeline always feeds replay accuracy into calibration.

## Issue Context
Source raw score and verdicts from the actual licensed verifier being calibrated, or run a credentialed API exam in a separately protected scheduled job.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[55-58]
- .github/workflows/verifier-exam.yml[162-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +70 to +74
- name: 入职考试(任一分项不过=exit 1 拒上岗)
id: exam
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
RUN_ID: ${{ github.run_id }} # zizmor:模板值经 env 间接引用

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Secret reaches pr code 🐞 Bug ⛨ Security

The exam step unconditionally injects LLM_API_KEY while executing run_exam.py from the pull
request checkout, including in default replay mode. A same-repository PR that modifies the runner
can read or exfiltrate the organization secret despite the comment claiming PR-modifiable code
cannot access it.
Agent Prompt
## Issue description
A secret is exposed to code controlled by pull-request changes.

## Issue Context
Do not inject the key in replay/PR jobs. Put credentialed API execution in a protected workflow/job that runs trusted code and is not triggered from an untrusted PR checkout.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[17-25]
- .github/workflows/verifier-exam.yml[60-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +188 to +195
python3 pipeline/verifier-exam/calibrate.py collect \
--repo "$REPO_FULL" --out calibration/samples.jsonl
- name: 出分即校准(CI 下界<及格线→needs_human 信号,不阻断)
run: |
set -euo pipefail
python3 pipeline/verifier-exam/calibrate.py score \
--samples calibration/samples.jsonl --raw-score "${RAW_ACC:-0.0}" \
--out calibration/report.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

10. Calibration history never accumulates 🐞 Bug ≡ Correctness

Each scheduled calibration run starts from a fresh checkout and creates a new local
calibration/samples.jsonl, because the workflow only uploads the file after scoring and never
restores a prior artifact or other durable store. As a result, append_samples can only
deduplicate/accumulate within a single run, and the seven-day collector window causes older
calibration evidence to be permanently lost over time.
Agent Prompt
## Issue description
Scheduled calibration runs do not restore prior `calibration/samples.jsonl` data, so each run starts with an empty/new local samples file and `append_samples` only deduplicates/accumulates within that single run. This prevents calibration history from building over time and causes events older than the collector window (e.g., seven days) to be permanently lost.

## Issue Context
The workflow performs a fresh checkout on each scheduled job, writes and scores `calibration/samples.jsonl`, and uploads it as an artifact, but does not download/restore that artifact (or otherwise persist the JSONL) at the start of the next run. Implement a durable storage approach with an explicit restore/update strategy (e.g., repository-backed versioned store or artifact download before collection and upload after scoring) so JSONL history and deduplication persist across runs, and ensure concurrent scheduled executions cannot overwrite each other.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[176-203]
- pipeline/verifier-exam/calibrate.py[85-100]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +119 to +122
rv = subprocess.run(["gh", "api", f"repos/{repo}/pulls/{pr['number']}/reviews",
"-f", "per_page=30"], capture_output=True, text=True, timeout=60)
if rv.returncode == 0:
pr["_reviews"] = json.loads(rv.stdout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

11. Review evidence is truncated 🐞 Bug ≡ Correctness

Reject classification examines only the first 30 reviews of an unmerged closed PR. If its
CHANGES_REQUESTED review appears on a later page, the PR is treated as ambiguous and skipped
despite having the required rejection evidence.
Agent Prompt
## Issue description
Review collection requests only one page of 30 reviews. This can omit a `CHANGES_REQUESTED` review and prevent a closed PR from becoming a reject calibration sample.

## Issue Context
`classify_pr` depends on `_reviews` containing a `CHANGES_REQUESTED` state. Fetch all relevant review pages before classification.

## Fix Focus Areas
- pipeline/verifier-exam/calibrate.py[66-73]
- pipeline/verifier-exam/calibrate.py[117-123]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +77 to +80
python3 pipeline/verifier-exam/run_exam.py \
--judge-config pipeline/verifier-exam/judge-configs/glm-4.5-air.json \
--judge-mode "$JUDGE_MODE" \
--out verifier-exam/results --run-id "$RUN_ID" > "$RUNNER_TEMP/exam.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

12. Selected model is ignored 🐞 Bug ≡ Correctness

The workflow dispatch exposes a model_alias selector, but the exam command always loads the
checked-in fixed GLM config and never uses the input, so dispatching a different candidate still
evaluates glm-4.5-air. This makes API model selection ineffective and can mislead results by
recording the wrong model identity, preventing the intended candidate-model entrance exam workflow.
Agent Prompt
## Issue description
The workflow-dispatch `model_alias` input is declared but never consumed by the exam execution. As a result, the exam always runs with the static checked-in GLM configuration (and records `glm-4.5-air`), so selecting another candidate model via API has no effect and produces misleading model identity/results.

## Issue Context
Update the workflow and runner so the requested alias meaningfully affects which judge/model configuration is used during the exam. Either (a) resolve `model_alias` to a trusted, allow-listed judge config path/identifier, or (b) generate an ephemeral configuration from the input while keeping fixed prompts/sampling/settings for comparability; alternatively, if dynamic selection is not intended, remove `model_alias` and require explicit config selection.

## Fix Focus Areas
- .github/workflows/verifier-exam.yml[23-27]
- .github/workflows/verifier-exam.yml[70-80]
- pipeline/verifier-exam/run_exam.py[169-184]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +171 to +181
s = judge_config.get("sampling") or {}
self.model = judge_config["model_alias"]
self.base_args = ["bash", str(repo_root / "scripts" / "llm-call.sh"), "--model", self.model]
if s.get("temperature") is not None:
self.base_args += ["--temperature", str(s["temperature"])]
if s.get("max_tokens") is not None:
self.base_args += ["--max-tokens", str(s["max_tokens"])]
if s.get("thinking"):
self.base_args += ["--thinking", str(s["thinking"])]
if s.get("seed") is not None:
self.base_args += ["--seed", str(s["seed"])]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

13. Top-p setting is not applied 🐞 Bug ≡ Correctness

The judge records top_p as a locked sampling parameter in the config and archives it in results as
if applied, but ApiJudge never forwards it and llm-call.sh has no --top-p option. This can
cause actual API behavior to differ from the sampling configuration claimed in the result,
undermining reproducibility when provider defaults differ from the config.
Agent Prompt
## Issue description
`top_p` is accepted in judge configurations and is archived in results as if it were applied, but `ApiJudge` never forwards it to provider calls and `llm-call.sh` does not currently accept or transmit a top-p value, so the recorded sampling configuration may not match actual API behavior.

## Issue Context
Implement consistent `top_p` support end-to-end (validate/accept via the wrapper, include in provider requests, and pass through from `ApiJudge`), and ensure any usage/recording schema reflects what was truly applied; alternatively, if `top_p` is not supported for a given pathway/provider, reject configs containing unsupported sampling fields or avoid recording them as “locked/applied” to prevent false reproducibility claims.

## Fix Focus Areas
- pipeline/verifier-exam/run_exam.py[169-181]
- pipeline/verifier-exam/run_exam.py[266-281]
- pipeline/verifier-exam/judge-configs/glm-4.5-air.json[5-10]
- scripts/llm-call.sh[24-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +135 to +138
for d in DIMENSIONS:
m = re.search(rf"^DIM\s+{d}\s+([01](?:\.\d+)?)\s*$", r.stdout, re.M | re.I)
dims[d] = {"score": float(m.group(1)) if m else None,
"basis": "api" if m else "unparseable"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

14. Api scores exceed rubric range 🐞 Bug ☼ Reliability

The rubric API parser accepts values such as 1.5 even though the rubric scale is [0,1], then
includes them directly in the recorded dimensional mean. A malformed judge response can therefore
produce an impossible shadow score instead of an explicit no-data debt.
Agent Prompt
## Issue description
The API-mode score regex accepts decimals above one (for example `1.5`) and stores them without validation, violating the rubric's declared `[0,1]` scale.

## Issue Context
Treat any missing, duplicate, malformed, non-finite, or out-of-range dimension score as unparseable/no-data so annotation debt is emitted rather than calculating an invalid mean.

## Fix Focus Areas
- pipeline/verifier-exam/rubric_shadow.py[119-139]
- pipeline/verifier-exam/rubric_shadow.py[166-182]
- pipeline/verifier-exam/rubrics/ai-readability-v1.yaml[5-7]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@randypanding
randypanding merged commit d22486a into main Aug 21, 2026
25 checks passed
@randypanding
randypanding deleted the w5c3-verifier-exam branch August 21, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants