chore(ci): skip Contract Guard on dependabot-authored PRs (#443) - #448
Conversation
Contract Guard demands two things dependabot structurally cannot supply: a scripts/tests/ contract test in the same PR whenever a critical file changes, and a structured GitNexus impact summary in the PR body. Its body is generated release notes and it does not touch tests, so every bump that edits .github/workflows/ fails permanently — #371, #373 and #378 have all been red since they opened, on exactly these five lines: - Missing contract test for critical file: .github/workflows/release.yml - Missing GitNexus impact summary field: Risk level (…and the other three fields) A check that can never pass does not add safety; it trains maintainers to skim past the check panel. repo-guard.yml already skips dependabot for the same reason. These bumps remain gated by CODEOWNERS review and by CI, which is the only required status check. Keyed on the PR author rather than github.actor, unlike repo-guard.yml: the moment a maintainer runs update-branch or pushes to a dependabot branch, actor becomes the maintainer and an actor-keyed skip stops applying — which is how this gate came back red on all three PRs while they were being processed. The author field does not move with the pusher. The test pins both halves, and also pins that .github/workflows/ is still classified critical, so the exemption gets revisited if that ever stops being true. Closes #443 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
代码评审报告: chore(ci): skip Contract Guard on dependabot-authored PRs (#443)
风险等级: 低
处理建议: 评论
决策摘要: 可以合并——改动就是一个 job 级 if:,非 dependabot PR 行为完全不变,author-keyed 的选择比 repo-guard.yml 的 actor-keyed 更正确;两处非阻塞问题:钉住"actor 形式必须缺席"的那条 doesNotMatch 正则抓不到 repo-guard.yml 实际使用的多行 if: > 写法,以及 docs/knowledge-contract.md 的 CI Contract 一节仍称该门禁覆盖所有指向 develop 的 PR。
级联分析
- 变更符号:
.github/workflows/contract-guard.yml的gitnexus-contractjob 级if:;新增测试contract guard skips dependabot-authored PRs by author, not actor。 - 受影响流程: 仅 GitHub PR 事件触发的 Contract Guard 运行。
scripts/workflows/contract-rules.mjs未改动(我读过全文,规则引擎行为不变),本地pnpm contract:gitnexus与 pre-push 路径不受影响。 - 变更集外调用方: 无。该 workflow 只有一个 job,仓库内没有任何
needs:或脚本读取Contract Guard / gitnexus-contract的结果(text search 覆盖.github/、scripts/)。CODEOWNERS 第 4 行确认.github/workflows/仍需 @ceilf6 审阅,PR 声称的兜底门禁成立。 - 置信度: medium(无代码图谱,结论来自 Read/Grep;变更面只有一个 workflow key,可穷举)
问题发现
-
[中]
doesNotMatch的 actor 正则抓不到repo-guard.yml的实际写法- 证据:
scripts/tests/workflow-rules.test.mjs:421的模式是/if:\s*github\.actor\s*!=\s*'dependabot\[bot\]'/u。而.github/workflows/repo-guard.yml:25-26的实际形式是if: >换行后接github.actor != 'dependabot[bot]' &&——if:与github.actor之间隔着一个>,\s*匹配不到。PR 描述把这条断言列为"pin 三件事"里的第 2 条:一次朝repo-guard.yml拼写靠拢的"一致性"编辑应当大声失败。恰恰是最可能被复制的那种写法会静默通过。 - 受影响调用方/流程: 仅测试的回归防护强度;无运行时影响。正向断言(415-418 行)依然有效,所以门禁本身不会因此变红。
- 最小可行修复: 去掉
if:前缀,改成/github\.actor\s*!=\s*'dependabot\[bot\]'/u。当前 workflow 注释里虽提到github.actor,但没有跟!= 'dependabot[bot]',不会误报。
- 证据:
-
[低] 知识契约文档仍描述"覆盖所有 develop PR"
- 证据:
docs/knowledge-contract.md:52"pnpm contract:gitnexusruns in Contract Guard on PRs todevelop",其后的检查清单没有豁免说明。docs/oss-harness-engineering-workflow.md:227写 "Treat CI and Contract Guard as minimum required checks",与 PR 正文"CI 是唯一 required status check"的表述存在轻微张力(分支保护配置在仓库内不可查证,我无法验证哪一方为准)。PR checklist 勾选了"behavior / Harness contracts 变更时已更新文档",但门禁适用范围确实变了。 - 受影响调用方/流程: 贡献者与 agent 依据
docs/knowledge-contract.md判断何时需要填 impact summary。 - 最小可行修复: 在
docs/knowledge-contract.md的 CI Contract 一节加一行——dependabot 作者的 PR 跳过该门禁,兜底为 CODEOWNERS 审阅 + CI。
- 证据:
行级发现
- [scripts/tests/workflow-rules.test.mjs:421] 正则要求
if:紧跟github.actor,而repo-guard.yml用的是if: >+ 换行的多行形式,正是"一致性"编辑最可能复制过来的写法,会静默通过这条doesNotMatch;去掉if:前缀,只匹配github\.actor\s*!=\s*'dependabot\[bot\]'。
Karpathy 评审
- 假设: 两个关键假设成立但值得记录。其一,author-keyed 意味着维护者推到 dependabot 分支上的任何提交(不止版本号)同样被豁免 Contract Guard——这是 actor-keyed 反向失效模式的镜像代价,由 CODEOWNERS + CI 兜底,PR 正文只讲了收益一侧。其二,若 Contract Guard 日后被加入分支保护,
if:跳过的 job 会以skipped结论上报(不是永久 pending,因为整个 workflow 仍会运行),不会死锁合并;workflow 只有一个 job,我已确认没有下游needs:依赖它。 - 简洁性: 一个 job 级
if:,无新增抽象、无 flag、无分支扩散。没有可见的更简写法。 - 结构质量: 无退化。豁免留在 workflow 触发层,规则引擎零改动,与 issue #443 的建议修复一致。
- 变更范围: 严格贴合目标——workflow / 钉住测试 / changelog,无夹带重构或格式噪声。
- 验证: 契约测试与 issue 验收标准(豁免存在 + 挂在作者上 + workflow 面仍属 critical,
contract-rules.mjs:70已确认)一一对应。端到端确认按设计只能在合并后观察,这一点 PR 已如实说明。
缺失覆盖
- 无合并前必须补的测试。剩余验证风险仅在合并后可观测:三个 dependabot PR 更新分支后
Contract Guard / gitnexus-contract应显示 skipped 而非 failed;若该 check 实际在分支保护的 required 列表里,需一并确认 skipped 被判为通过。
| ); | ||
| assert.doesNotMatch( | ||
| contractGuard, | ||
| /if:\s*github\.actor\s*!=\s*'dependabot\[bot\]'/u, |
There was a problem hiding this comment.
正则要求 if: 紧跟 github.actor,而 repo-guard.yml 用的是 if: > + 换行的多行形式,正是"一致性"编辑最可能复制过来的写法,会静默通过这条 doesNotMatch;去掉 if: 前缀,只匹配 github\.actor\s*!=\s*'dependabot\[bot\]'。
…d document the exemption repo-guard on #448, both findings. The doesNotMatch pattern required `if:` to sit immediately before `github.actor`, but repo-guard.yml writes it as `if: >` followed by a newline — exactly the spelling a later "make these two consistent" edit would copy. The one form the assertion most needed to catch was the one it could not see. Dropping the prefix; the multi-line form now fails it, verified. docs/knowledge-contract.md still described Contract Guard as covering every PR to develop, which is where contributors and agents look to decide whether an impact summary is required. It also now records the cost repo-guard named: an author-keyed skip exempts *any* commit on a dependabot branch, including one a maintainer pushes, so such a push should be reviewed as if the gate had not run. Refs #443 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
两条都已处理( 发现 1(中,正则抓不到多行 发现 2(低,文档) — 已在 关于 |
There was a problem hiding this comment.
代码评审报告: chore(ci): skip Contract Guard on dependabot-authored PRs (#443)
风险等级: 低
处理建议: 评论
决策摘要: 可以合并——豁免作用域正确、自证成立、与 issue #443 的验收要求逐条对应;唯一非阻塞项是钉住测试的负向断言覆盖注释行,未来在 workflow 注释里写出被拒绝的 actor 写法会误红。
级联分析
- 变更符号: 无。diff 只新增
gitnexus-contract的 job 级if:键、一条 workflow 测试、一段文档、一行 changelog;contract-rules.mjs未改动。 - 受影响流程: 仅
Contract Guard在pull_request → develop上的门禁触发层。.github/workflows/contract-guard.yml:3-6确认唯一触发事件是pull_request,因此github.event.pull_request.user.login在所有触发路径上均有定义,表达式不会退化成空串比较 (read)。 - 变更集外调用方:
evaluateGitNexusContract/classifyContractPaths/criticalContractRules的仓内引用只有scripts/workflows/contract-check.mjs:8,63和scripts/tests/workflow-rules.test.mjs,两者语义均未变 (text search)。规则引擎行为不变,豁免完全停留在触发层,与 PR 描述一致。 - 置信度: medium — 无代码图谱,结论来自对 4 个变更文件与
contract-rules.mjs、repo-guard.yml、dependabot.yml的直接阅读加全仓文本搜索;分支保护配置不在仓库内,"CI 是唯一 required check" 这一前提无法在 checkout 中验证。
已核对且成立的关键点:
contract-rules.mjs:70的repo-harness规则确实覆盖.github/workflows/,所以本 PR 自身受门禁约束,并按要求携带了scripts/tests/测试与结构化 impact summary;docs/knowledge-contract.md属authority-docs(contract-rules.mjs:88),同一个测试文件同时满足该规则的testPattern。repo-guard.yml:25-26确为if: >多行形式且首行是github.actor != 'dependabot[bot]',测试注释对"最可能被复制过来的写法"的描述属实,负向断言不加if:前缀是必要的。- job 级
if:为 false 时 GitHub 仍会发布该 check run(conclusionskipped),不会让分支保护停在 pending,因此即便该 check 被设为必需也不会死锁。
问题发现
-
[低] 负向断言作用于整份 workflow 原文,包含注释行
- 证据:
scripts/tests/workflow-rules.test.mjs:422-426对readFileSync('.github/workflows/contract-guard.yml')的全文做doesNotMatch(/github\.actor\s*!=\s*'dependabot\[bot\]'/u)。当前.github/workflows/contract-guard.yml:26的注释写成github.actor:才侥幸不匹配。 - 受影响调用方/流程:
pnpm test:workflows(quality:precommit/quality:local/ CI 均包含)。后续任何人在该 workflow 注释里完整写出被拒绝的github.actor != 'dependabot[bot]'作为反例说明,都会让这条测试在行为完全正确的情况下失败。 - 最小可行修复: 断言前先剔除注释行,例如
const active = contractGuard.split('\n').filter((l) => !l.trimStart().startsWith('#')).join('\n'),正负断言都对active执行。语义不变,误红面消失。
- 证据:
-
[低] 豁免的实际边界比"dependabot 的自动升级"更宽,属已知取舍
- 证据: 判定挂在 PR 作者上,因此维护者推到
dependabot/*分支的任意提交(包括对.github/workflows/的手写改动)同样不过门禁;repo-guard.yml因为仍是 actor-keyed,这种推送反而照跑。 - 受影响调用方/流程: Contract Guard 对 dependabot 分支上人工提交的覆盖。
- 最小可行修复: 无需改代码——这正是 issue #443 明确要求的取舍,且
docs/knowledge-contract.md:64-66已写明"review such a push as if the gate had not run"。此处仅作为合并后需要维护者知晓的剩余风险记录。
- 证据: 判定挂在 PR 作者上,因此维护者推到
行级发现
- [scripts/tests/workflow-rules.test.mjs:424] 该负向断言匹配整份 workflow 原文,注释行也算数;未来在 workflow 注释中写出这条被拒绝的 actor 写法作为反例会让测试无故失败。断言前过滤掉以
#开头的行即可。
Karpathy 评审
- 假设: 两个外部假设未在仓库内可验证——
CI是唯一 required status check,以及 dependabot 的 PR 目标分支是develop(.github/dependabot.yml未设target-branch,依赖默认分支)。两者都由 issue #443 中的真实失败 run 输出间接佐证:门禁确实在 dependabot PR 上跑过并失败,说明目标分支正确。其余假设(作者字段不随推送者变化、skipped job 计为通过)成立。 - 简洁性: 一个 job 级
if:,无新抽象、无 flag、无条件分支扩散。没有可见的更简结构。 - 结构质量: 无退化。豁免放在触发层而不是塞进
contract-rules.mjs的规则引擎,是正确的归属层级——规则引擎保持对"什么是 critical"的单一职责,不掺入"谁提的 PR"。与repo-guard.yml的写法差异有明确理由并已在两处注释和文档中说明,不是无意的语义分叉。 - 变更范围: 严格贴合目标,4 个文件无一行溢出。注释密度偏高但每段都在解释非显然的取舍(为什么豁免、为什么用 author 而非 actor),符合该目录既有风格。
- 验证: 与风险等级相称。测试同时钉住正向形式、负向形式和豁免的前提条件(
.github/workflows/仍属 critical),第三条尤其好——它让豁免在前提消失时可被发现。端到端确认只能在合并后取得,PR 已如实说明这一点。
缺失覆盖
- 无需在合并前补充。真正的端到端证据(三个 dependabot PR 上
Contract Guard / gitnexus-contract报 skipped 而非 failed)只能在合并后取得;合并后请确认一次,若这些 PR 的目标分支并非develop,则该门禁本就不该在其上运行,本豁免的前提需要重新核对。
| // github.actor 会让它静默通过这条断言。 | ||
| assert.doesNotMatch( | ||
| contractGuard, | ||
| /github\.actor\s*!=\s*'dependabot\[bot\]'/u, |
There was a problem hiding this comment.
该负向断言匹配整份 workflow 原文,注释行也算数;未来在 workflow 注释中写出这条被拒绝的 actor 写法作为反例会让测试无故失败。断言前过滤掉以 # 开头的行即可。
repo-guard on #448, finding 1. The negative assertion deliberately omits the `if:` prefix so it catches repo-guard.yml's multi-line form — but that wider pattern also reads comment lines, and only a stray colon in the workflow's own comment kept it from matching. Writing the rejected expression out as a counter-example, which is the natural thing to do, would have turned the gate red on correct code. Comment lines are dropped before both assertions. The workflow comment now does spell the rejected form out, and records the cost repo-guard named in its second finding: an author-keyed skip also exempts commits a maintainer pushes to a dependabot branch. Refs #443 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
代码评审报告: chore(ci): skip Contract Guard on dependabot-authored PRs (#443)
风险等级: 低
处理建议: 评论
决策摘要: 可以合并——if: 挂在 PR 作者上正确解决了 issue #443 描述的结构性必红,且不影响非 dependabot PR;唯一非阻塞问题是新测试第三条断言用源码正则而非该文件已有的 classifyContractPaths() 行为断言。
级联分析
- 变更符号: 无。diff 只新增一个 job 级
if:键、一个测试、一段文档和一行 changelog;contract-rules.mjs/contract-check.mjs未被触碰(已读取确认evaluateGitNexusContract、classifyContractPaths与 PR 前一致)。 - 受影响流程:
Contract Guard / gitnexus-contractjob 的触发条件;PR 作者为dependabot[bot]时 job 报 skipped,其余路径与合并前逐字相同(.github/workflows/contract-guard.yml:36-52未变)。 - 变更集外调用方: 无程序化消费方。规则引擎的唯一 in-repo 调用方是
scripts/workflows/contract-check.mjs和本测试文件(text search 确认),二者行为不变。门禁结果的实际消费者是 branch protection 与维护者,位于仓库之外。 - 置信度: medium — 无代码图谱,结论基于 diff + 直接读取
contract-guard.yml、contract-rules.mjs、repo-guard.yml、CODEOWNERS、knowledge-contract.md(text search)。我没有执行测试套件,也无法从 checkout 验证 "CI 是唯一 required status check" 这一分支保护设置。
已验证的关键点:
.github/workflows/确实落在contract-rules.mjs:70的repo-harness规则里,所以本 PR 自我门禁成立,且它带了scripts/tests/下的测试改动;docs/knowledge-contract.md属于authority-docs(contract-rules.mjs:88),同一份测试改动同时满足该条。- 负向断言不会被自身注释误触发:
contract-guard.yml:27的注释里确实写着github.actor != 'dependabot[bot]',而测试在workflow-rules.test.mjs:414-417先过滤掉#开头的行,注释被剥离后doesNotMatch才生效——这是 c70102c 修的问题,按行读代码逻辑上成立。 - 作者键控优于 actor 键控的论点成立:
repo-guard.yml:26用的是github.actor,而 update-branch / 补推会把 actor 变成维护者;github.event.pull_request.user.login不随推送者变化。 - 兜底门禁在仓库内确实存在:
CODEOWNERS:4覆盖.github/workflows/(是否强制仍取决于分支保护)。
问题发现
-
[低] 新测试第三条断言用源码正则,偏离同文件已确立的行为断言模式
- 证据:
scripts/tests/workflow-rules.test.mjs:418,435读取contract-rules.mjs源码文本并匹配/file\.startsWith\('\.github\/workflows\/'\)/u。而同一文件在:53、:69、:134都是直接调用classifyContractPaths([...])断言category,且classifyContractPaths已在:18导入。 - 受影响调用方/流程: 只影响本测试自身的可靠性。它在两个方向上都不准:把
startsWith('.github/workflows/')改写成等价的正则或数组.some()(甚至只是 Prettier 改动引号/换行)会让一条纯粹的重构变红;反过来,若该字符串日后落在死代码或另一条规则里,断言仍会通过,而.github/workflows/实际已不再 critical——正是注释(:433-434)声称要防的那种情况。 - 最小可行修复: 换成行为断言,并删掉
:418的readFileSync(rules):assert.deepEqual(classifyContractPaths(['.github/workflows/contract-guard.yml']).critical, [{ file: '.github/workflows/contract-guard.yml', category: 'repo-harness' }]);
- 证据:
-
[低] 安全性论证依赖两项仓库外设置,checkout 内无法核实
- 证据: PR 正文与
CHANGELOG.md:13都写 "CI, which is the only required status check",docs/knowledge-contract.md:64写这些 PR 由 "CODEOWNERS review and by CI" 兜底。仓库内只能确认CODEOWNERS:4覆盖了.github/workflows/;required checks 与 "Require review from Code Owners" 均是 branch protection 配置。 - 受影响调用方/流程: dependabot 改
.github/workflows/时的实际把关强度。 - 最小可行修复: 不需要改代码。合并后按 PR 里写的计划确认三个 dependabot PR 上
Contract Guard / gitnexus-contract报 skipped,并顺手确认 code owner review 为必需即可。
- 证据: PR 正文与
行级发现
- [scripts/tests/workflow-rules.test.mjs:435] 用源码正则匹配
contract-rules.mjs会在等价重构或格式化时误红,也无法真正证明.github/workflows/仍被分类为 critical;改用同文件已有的行为断言classifyContractPaths(['.github/workflows/contract-guard.yml']).critical[0].category === 'repo-harness',并删除第 418 行不再需要的readFileSync。
Karpathy 评审
- 假设: 关键假设已被显式写出而非埋在代码里——
contract-guard.yml:31-33明确承认这条豁免比"仅 dependabot 自动升级"更宽(维护者推到 dependabot 分支的提交同样不过门禁),docs/knowledge-contract.md:64-66用同样措辞记录,并要求按"门禁没跑过"来审。这是正确的处理方式:把扩大的范围写成契约,而不是留给下一个人去发现。未落地的假设只剩上面第 2 条的分支保护设置。 - 简洁性: 单个 job 级
if:,没有新增 flag、mode、helper 或配置层,规则引擎零改动。豁免留在 workflow 触发层而不是渗进contract-rules.mjs,这是正确的归属层——规则引擎不需要知道 bot 身份。 - 结构质量: 无退化。没有新抽象、没有共享路径特例、没有文件跨越可扫描边界(
contract-guard.yml53 行)。注释密度偏高(17 行新增里 16 行是注释),但内容是决策理由而非复述代码,与该仓库 workflow 文件的既有风格一致(对比repo-guard.yml:3-6,23-24,57-60),不作为 finding。 - 变更范围: 与既定目标严格对齐。4 个文件、+58/-0、无无关重构、无格式噪声;文档与 changelog 是该仓库对 critical 变更的既定要求,不算范围蔓延。
- 验证: 正向断言、负向断言、前提断言三条覆盖了作者键控这一核心行为。负向断言尤其有价值——它把"向
repo-guard.yml拼写看齐"这个具体回归路径钉死,且刻意不带if:前缀以覆盖if: >多行形式。缺口只在第三条断言的实现方式(finding 1)。
缺失覆盖
- 无合并前必须补的测试。
if:表达式本身在 GitHub Actions 求值,无法在 node test 里执行,用文本断言钉住是这里唯一可行的手段,PR 已经做了。 - 端到端确认只能在合并后取得(三个 dependabot PR 上门禁报 skipped 而非 failed),PR 正文已如实说明这一点,未把它伪装成已完成的验证。
| ); | ||
| // 这条豁免的前提:workflow 文件确实属于 critical 面,否则跳过就是无谓放松。 | ||
| // 若 repo-harness 规则日后不再覆盖 .github/workflows/,本豁免应一并复审。 | ||
| assert.match(rules, /file\.startsWith\('\.github\/workflows\/'\)/u); |
There was a problem hiding this comment.
用源码正则匹配 contract-rules.mjs 会在等价重构或格式化时误红,也无法真正证明 .github/workflows/ 仍被分类为 critical;改用同文件已有的行为断言 classifyContractPaths(['.github/workflows/contract-guard.yml']).critical[0].category === 'repo-harness',并删除第 418 行不再需要的 readFileSync。
repo-guard on #448. The third assertion grepped contract-rules.mjs for `file.startsWith('.github/workflows/')`, so an equivalent rewrite of that predicate would read as a contract change. Every other assertion in this file calls classifyContractPaths() directly; this one now does too. Refs #443 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
代码评审报告: chore(ci): skip Contract Guard on dependabot-authored PRs (#443)
风险等级: 低
处理建议: 批准
决策摘要: 可以合并——豁免只落在 workflow 触发层,规则引擎与所有非 dependabot PR 行为完全不变,且 CODEOWNERS + CI 的兜底在仓库内可验证;唯一残留是一处测试断言的注释剥离只覆盖整行注释。
级联分析
- 变更符号: 无。diff 只有
contract-guard.yml的一个 job 级if:、一个新测试、docs 与 CHANGELOG 各一段。scripts/workflows/contract-rules.mjs未被触碰(graph 不可用,已按只读核对源码)。 - 受影响流程:
Contract Guard / gitnexus-contract在 PR→develop 上的触发判定。gitnexus-contract是该 workflow 的唯一 job,if:为假时整条 workflow 被跳过。本地pnpm contract:local/contract:check走scripts/workflows/contract-check.mjs,不读该if:,路径不受影响。 - 变更集外调用方:
evaluateGitNexusContract/classifyContractPaths的仓库内调用方经全仓文本搜索确认只有scripts/workflows/contract-check.mjs:63与本测试文件 (text search),均未改变语义。quality:ci(package.json:29) 不含contract:gitnexus,所以跳过 Contract Guard 不会让同一检查从CI里再红一次——兜底路径真实成立。.github/CODEOWNERS:4覆盖.github/workflows/,PR 正文的「CODEOWNERS 人工审阅兜底」在仓库内可核实。 - 置信度: medium — 无代码图谱,调用方由文本搜索确认;分支保护配置(「
CI是唯一 required check」)不在 checkout 内,无法核实,但即使 Contract Guard 被设为 required,job 级if:跳过在 GitHub 下按成功计,两种情形都不阻塞。
问题发现
- [低] 测试的注释剥离只处理整行注释,行尾注释仍会触发负向断言
- 证据:
scripts/tests/workflow-rules.test.mjs:416用!line.trimStart().startsWith('#')过滤。负向断言/github\.actor\s*!=\s*'dependabot\[bot\]'/u不带if:前缀,因此把被否决的 actor 写法写成行尾注释(例如在if:那行后追加说明)会让测试红,而这本身是正确的文档行为——恰是该过滤器想解决的那类误报,只解决了一半。 - 受影响调用方/流程:
pnpm test:workflows(quality:precommit/quality:ci),只影响后续编辑该 workflow 的贡献者,不影响本次合并。 - 最小可行修复: 把负向断言限定在有效的
if:表达式行上(例如先取出if:那一行再断言),而不是扩大注释剥离——朴素地剥离行尾#会误伤含#的 YAML 字符串。
- 证据:
(无 blocking findings。已核对:if: 位于 job 级、语法合法;github.event.pull_request.user.login 在本 workflow 唯一的 pull_request 触发下必然存在;作者字段不可被 PR 内容影响,无伪装面。)
行级发现
- [scripts/tests/workflow-rules.test.mjs:416] 只剥离整行注释,行尾注释里的
github.actor != 'dependabot[bot]'反例仍会让下方负向断言失败;建议把断言锚定到取出的if:表达式行,而非依赖更激进的注释剥离。
Karpathy 评审
- 假设: 关键假设「dependabot 结构上给不出
scripts/tests/契约测试与结构化 impact summary」由contract-rules.mjs:66-78(repo-harness规则含.github/workflows/)与validateStructuredImpactSummary直接支持;.github/dependabot.yml:27的 github-actions 生态确认这类 PR 会持续产生。豁免比「dependabot 自动升级」更宽(维护者推到 dependabot 分支同样跳过)这一点没有被藏起来,contract-guard.yml:31-33与docs/knowledge-contract.md:64-66都明写了,并给出了「按门禁没跑过来审」的处置。 - 简洁性: 一个 job 级
if:,未新增 flag、层级或规则引擎分支。相对可选方案(在规则引擎里给 dependabot 关掉 impact summary)更简单,且那条路也解决不了缺契约测试的那一半。 - 结构质量: 无退化。豁免留在触发层,
criticalContractRules不被特例污染,兄弟流程(repo-guard.yml)语义差异有明确理由记录。新测试的注释剥离是一行局部逻辑,未与既有 helper 重复。 - 变更范围: 与既定目标严格对应,无顺手重构或格式噪声。
- 验证: 新测试同时钉住正向表达式、actor 写法的缺席,以及
.github/workflows/仍被classifyContractPaths判为repo-harness——第三条断言的是行为而非源码文本,与文件其余部分风格一致,是这里的正确选择。本 PR 自身改动.github/workflows/与docs/knowledge-contract.md,两条 critical 规则的测试要求都由本测试满足,门禁自洽。
缺失覆盖
- 无合并前必须补充的测试。端到端确认只能在合并后取得:三个 dependabot PR 更新分支后,
Contract Guard / gitnexus-contract应报 skipped 而非 failed;若届时仍为 failed,说明这些 PR 的 base 或触发假设与预期不符,需要回看。
| // 门禁变红。 | ||
| const contractGuard = readFileSync('.github/workflows/contract-guard.yml', 'utf8') | ||
| .split('\n') | ||
| .filter((line) => !line.trimStart().startsWith('#')) |
There was a problem hiding this comment.
只剥离整行注释,行尾注释里的 github.actor != 'dependabot[bot]' 反例仍会让下方负向断言失败;建议把断言锚定到取出的 if: 表达式行,而非依赖更激进的注释剥离。
Linked Issue Or Context
Summary
Contract Guard / gitnexus-contractfails on any PR that edits a critical file unless the same PR carries ascripts/tests/contract test and a structured GitNexus impact summary in the PR body. dependabot can supply neither — its body is generated release notes and it does not touch tests — so every bump that edits.github/workflows/is red from the moment it opens.#371, #373 and #378 have all been failing on exactly this:
A check that can never pass does not add safety; it trains maintainers to skim past the check panel.
repo-guard.ymlalready skips dependabot for the same reason. These bumps stay gated by CODEOWNERS review and byCI, the only required status check.Keyed on the PR author, not
github.actor— deliberately different fromrepo-guard.yml. The moment a maintainer runs update-branch or pushes to a dependabot branch,github.actorbecomes the maintainer and an actor-keyed skip stops applying. That is not hypothetical: it is how this gate came back red on all three PRs while they were being worked through today. The author field does not move with the pusher.Impact Scope
.github/workflows/contract-guard.yml— one job-levelif:.scripts/tests/workflow-rules.test.mjs— one test.CHANGELOG.md—[Unreleased] → Changed.No application code. Non-dependabot PRs are unaffected: the job runs exactly as before.
GitNexus Impact Summary
.github/workflows/contract-guard.ymlclassifies asrepo-harnessundercriticalContractRules, so this PR gates itself and ships the required contract test.detect_changesreports no symbol-level change — the diff is one workflow key, one test, one changelog line.contextonevaluateGitNexusContract/classifyContractPathsshows their only in-repo callers arescripts/workflows/contract-check.mjsand this test file; neither is touched, so the rule engine's behaviour is unchanged and the exemption lives entirely at the workflow trigger layer. Noimpactquery applies — nothing downstream of the gate reads its result programmatically.pnpm quality:precommit(lint / typecheck / test / test:workflows, 41 workflow tests) and the fullpnpm quality:localpre-push gate both passed.Verification
contract guard skips dependabot-authored PRs by author, not actorpins three things:if:is present;repo-guard.yml's spelling fails loudly instead of quietly restoring the red;.github/workflows/is still classified critical incontract-rules.mjs— if that ever stops being true this exemption is pointless and should be revisited, and the test says so.End-to-end confirmation comes after merge: the three dependabot PRs get their branches updated, and
Contract Guard / gitnexus-contractshould report skipped rather than failed.Checklist
pnpm quality:precommit, or explained why it could not run.pnpm quality:localfor critical skeleton changes, or explained why it could not run.