fix(ci): 修复引擎 bump 自动合并的守卫,并补回 Issue 模板的脱敏警告

engine-bump-triage.yml 里 `&& steps.lock.outputs.locked == 'yes'` 被写进了 HEAD 这个环境变量的值,而不是步骤的 if:。它在那个位置根本不是条件:不论契约锁校验通过与否,自动合并都会执行;步骤随后发出的评论还会把 commit 后面跟上字面量 `&& steps.lock.outputs.locked == 'yes'` 一起打印出来。因为没有任何东西失败,这个问题没有暴露过。

(实际风险有限:Product inputs 是必需检查,未审的 bump 不会真的被放行。但这条守卫本身写了等于没写。)

补 tests/test_engine_bump_triage.py 三个断言:自动合并的条件必须同时包含判定和锁;任何步骤的 env 值里都不许再出现条件片段;评论用的 BASE/HEAD 必须只是 range 输出本身。第二条是通用的,同类错误以后写在别的步骤上也会被抓到。

同时补回本仓 bug_report.yml 相对组织模板丢掉的两项:日志字段的「不要粘贴真实输入内容、用户词库、API Key 或任何凭据」加粗警告,以及「这不是安全漏洞」的必选确认项。全组织的 Issue 流量几乎都在这个仓库,恰恰是最需要这两条的地方——输入法的日志天然贴近用户真实输入。
This commit is contained in:
Evan Hu committed 2026-09-06 18:15:51 +09:00
1 parent e24913e9e3
commit 88720a80df
4 files changed
+62 -4

No files matched your search

+4 -2
View File
@@ -102,8 +102,8 @@ body:
attributes:
label: 日志(可选)
description: |
可粘贴相关日志。
Paste relevant logs, or attach screenshots / recordings.
可粘贴相关日志或附上截图。**不要粘贴真实输入内容、用户词库、API Key 或任何凭据。**
Paste relevant logs. Do not include real typed text, user dictionary contents, API keys or credentials.
render: shell
validations:
required: false
@@ -123,3 +123,5 @@ body:
required: true
- label: 我已尽量提供可复现信息。I provided enough detail to reproduce the issue.
required: true
- label: 这不是安全漏洞。安全问题请按 SECURITY.md 私下上报。This is not a security vulnerability; those go through SECURITY.md.
required: true
+4
View File
@@ -37,6 +37,10 @@ jobs:
run: python3 -m unittest discover -s tests -p 'test_prepare_env.py'
- name: Test the automatic release CI gate
run: python3 -m unittest discover -s tests -p 'test_release_pr_ci.py'
- name: Test the engine-bump auto-merge guards
run: |
python3 -m pip install --quiet pyyaml
python3 -m unittest discover -s tests -p 'test_engine_bump_triage.py'
- name: Read the reviewed lock
id: lock
run: |
+6 -2
View File
@@ -130,10 +130,14 @@ jobs:
fi
- name: Enable auto-merge
if: steps.classify.outputs.verdict == 'inert'
# The lock guard belongs here. It used to be appended to the HEAD environment value instead,
# where it was not a condition at all: auto-merge ran whether or not the contract lock
# verified, and the comment below printed the commit followed by the literal text
# "&& steps.lock.outputs.locked == 'yes'".
if: steps.classify.outputs.verdict == 'inert' && steps.lock.outputs.locked == 'yes'
env:
BASE: ${{ steps.range.outputs.base }}
HEAD: ${{ steps.range.outputs.head }} && steps.lock.outputs.locked == 'yes'
HEAD: ${{ steps.range.outputs.head }}
run: |
set -euo pipefail
gh pr merge --auto --squash "$PR_URL"
+48
View File
@@ -0,0 +1,48 @@
"""Guard the conditions on the engine-bump auto-merge step.
This exists because of a specific failure: the contract-lock guard was written into the step's HEAD
environment value rather than its `if:`, so it was not a condition at all. Auto-merge ran whether or
not the lock verified, and the comment the step posts printed the commit followed by the literal
text `&& steps.lock.outputs.locked == 'yes'`. Nothing failed, so nothing surfaced it.
"""
from pathlib import Path
import unittest
import yaml
WORKFLOW = Path(__file__).resolve().parents[1] / '.github/workflows/engine-bump-triage.yml'
def steps():
# `on:` parses as the boolean True in YAML 1.1, which is why the job lookup goes through 'jobs'.
workflow = yaml.safe_load(WORKFLOW.read_text(encoding='utf-8'))
return [step for job in workflow['jobs'].values() for step in job.get('steps', [])]
def step_named(name):
matches = [step for step in steps() if step.get('name') == name]
if len(matches) != 1:
raise AssertionError(f'expected exactly one step named {name!r}, found {len(matches)}')
return matches[0]
class AutoMergeGuards(unittest.TestCase):
def test_auto_merge_requires_both_the_verdict_and_the_lock(self):
condition = step_named('Enable auto-merge')['if']
self.assertIn("steps.classify.outputs.verdict == 'inert'", condition)
self.assertIn("steps.lock.outputs.locked == 'yes'", condition)
def test_no_step_condition_leaked_into_an_environment_value(self):
for step in steps():
for key, value in (step.get('env') or {}).items():
self.assertNotIn('steps.', str(value).split('}}')[-1],
f'{step.get("name")}: env {key} carries a condition fragment')
def test_the_comment_prints_only_the_commit_range(self):
env = step_named('Enable auto-merge')['env']
for key in ('BASE', 'HEAD'):
self.assertRegex(env[key], r'^\$\{\{ steps\.range\.outputs\.\w+ \}\}$')
if __name__ == '__main__':
unittest.main()