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:
1 parent
e24913e9e3
commit
88720a80df
4 files changed
+62
-4
No files matched your search
@@ -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
|
||||
@@ -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: |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user