ci(pr-risk): publish the advisory grade as a neutral Check Run - #15442
ci(pr-risk): publish the advisory grade as a neutral Check Run#15442mattmillerai wants to merge 6 commits into
Conversation
The `risk:*` label is mutable and carries no history, so a grade that is later disputed cannot be checked against the reasoning that produced it. Opting into the grader's `check_run` input publishes the tier and the per-axis reason table as a commit-attached Check Run. The upstream conclusion is hardcoded `neutral` and the publish step always exits 0, so nothing here can fail a PR. `checks: write` is already granted in this caller — every caller on this pin owes that grant whether or not it opts in, because GitHub validates the nested `publish-check` job's declaration at startup regardless of its `if:`.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🎭 Playwright: ✅ 1839 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-21T07:30:41.715Z",
"gitSha": "45f3b24efeff7babd7419d41836a53f4ef1c393d",
"branch": "matt/be-8038-pr-risk-check-run",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2141.9550000000527,
"styleRecalcs": 7,
"styleRecalcDurationMs": 6.171000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 680.3760000000001,
"heapDeltaBytes": 15596100,
"heapUsedBytes": 76857100,
"domNodes": -284,
"jsHeapTotalBytes": 4710400,
"scriptDurationMs": 10.9,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2042.7349999999933,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.65,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 509.30299999999994,
"heapDeltaBytes": 18782936,
"heapUsedBytes": 80538692,
"domNodes": -282,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 7.585,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1974.316999999985,
"styleRecalcs": 78,
"styleRecalcDurationMs": 51.214999999999996,
"layouts": 12,
"layoutDurationMs": 4.699,
"taskDurationMs": 988.4219999999999,
"heapDeltaBytes": 5163552,
"heapUsedBytes": 66614012,
"domNodes": -283,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 123.196,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1807.2220000000243,
"styleRecalcs": 73,
"styleRecalcDurationMs": 33.102,
"layouts": 12,
"layoutDurationMs": 3.357,
"taskDurationMs": 817.908,
"heapDeltaBytes": -4814828,
"heapUsedBytes": 56221604,
"domNodes": -280,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 95.669,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1723.0090000000473,
"styleRecalcs": 33,
"styleRecalcDurationMs": 16.902,
"layouts": 6,
"layoutDurationMs": 0.5790000000000001,
"taskDurationMs": 361.72099999999995,
"heapDeltaBytes": 3142608,
"heapUsedBytes": 64408548,
"domNodes": 79,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 10.148000000000001,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1738.0660000000034,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.121,
"layouts": 6,
"layoutDurationMs": 0.707,
"taskDurationMs": 349.002,
"heapDeltaBytes": 2483488,
"heapUsedBytes": 63860348,
"domNodes": 77,
"jsHeapTotalBytes": 4194304,
"scriptDurationMs": 9.252,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 579.9339999999802,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.03,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 370.21000000000004,
"heapDeltaBytes": 10834264,
"heapUsedBytes": 71311408,
"domNodes": 18,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 54.74900000000001,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "dom-widget-clipping",
"durationMs": 558.5849999999937,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.358999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 333.769,
"heapDeltaBytes": 10710864,
"heapUsedBytes": 72140564,
"domNodes": 20,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 48.18899999999999,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2072.47000000001,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.739999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 684.6290000000001,
"heapDeltaBytes": -5909124,
"heapUsedBytes": 70167100,
"domNodes": -282,
"jsHeapTotalBytes": -2363392,
"scriptDurationMs": 17.021,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2071.390000000065,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.913,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 680.749,
"heapDeltaBytes": -2858300,
"heapUsedBytes": 72874000,
"domNodes": -270,
"jsHeapTotalBytes": -1314816,
"scriptDurationMs": 17.512000000000004,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2196.9369999999913,
"styleRecalcs": 70,
"styleRecalcDurationMs": 17.350000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1242.684,
"heapDeltaBytes": 12894076,
"heapUsedBytes": 88706848,
"domNodes": -284,
"jsHeapTotalBytes": 4415488,
"scriptDurationMs": 325.244,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2167.1010000000024,
"styleRecalcs": 70,
"styleRecalcDurationMs": 18.314,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1191.429,
"heapDeltaBytes": -5157356,
"heapUsedBytes": 71548048,
"domNodes": -269,
"jsHeapTotalBytes": -565248,
"scriptDurationMs": 314.083,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3264.1179999999963,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.667,
"layouts": 60,
"layoutDurationMs": 8.416,
"taskDurationMs": 1451.78,
"heapDeltaBytes": -7699588,
"heapUsedBytes": 70259316,
"domNodes": -286,
"jsHeapTotalBytes": 3928064,
"scriptDurationMs": 391.515,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3164.0409999999974,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.865,
"layouts": 60,
"layoutDurationMs": 7.934,
"taskDurationMs": 1338.953,
"heapDeltaBytes": -8853664,
"heapUsedBytes": 69374344,
"domNodes": 12,
"jsHeapTotalBytes": 5804032,
"scriptDurationMs": 387.444,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "legacy-node-drag",
"durationMs": 2327.6950000000056,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.738000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1508.6360000000002,
"heapDeltaBytes": -14236816,
"heapUsedBytes": 69590912,
"domNodes": 12,
"jsHeapTotalBytes": 8425472,
"scriptDurationMs": 481.30699999999996,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "legacy-node-drag",
"durationMs": 2206.8080000000236,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.616,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1436.871,
"heapDeltaBytes": -13710080,
"heapUsedBytes": 70543124,
"domNodes": 12,
"jsHeapTotalBytes": 8425472,
"scriptDurationMs": 474.15299999999996,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2028.8529999999696,
"styleRecalcs": 6,
"styleRecalcDurationMs": 5.603000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 725.746,
"heapDeltaBytes": -12389128,
"heapUsedBytes": 69969156,
"domNodes": -284,
"jsHeapTotalBytes": 3403776,
"scriptDurationMs": 19.809,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2068.9889999999878,
"styleRecalcs": 8,
"styleRecalcDurationMs": 5.971000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 634.803,
"heapDeltaBytes": -12992380,
"heapUsedBytes": 69204456,
"domNodes": -283,
"jsHeapTotalBytes": 3928064,
"scriptDurationMs": 14.238000000000001,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 611.814000000038,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.738,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 402.75699999999995,
"heapDeltaBytes": 10748020,
"heapUsedBytes": 72403016,
"domNodes": 20,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 123.37700000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 584.2229999999518,
"styleRecalcs": 45,
"styleRecalcDurationMs": 8.186999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 382.259,
"heapDeltaBytes": 11134348,
"heapUsedBytes": 72473044,
"domNodes": 16,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 118.03800000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2048.9130000000273,
"styleRecalcs": 8,
"styleRecalcDurationMs": 10.395000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 542.1949999999999,
"heapDeltaBytes": 16180744,
"heapUsedBytes": 77763296,
"domNodes": -282,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 8.783000000000003,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2018.0820000000494,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.755,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 521.6999999999999,
"heapDeltaBytes": 16952468,
"heapUsedBytes": 78539380,
"domNodes": -283,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 7.724,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1739.2970000000219,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.443999999999996,
"layouts": 16,
"layoutDurationMs": 4.779,
"taskDurationMs": 832.681,
"heapDeltaBytes": 20536156,
"heapUsedBytes": 81906948,
"domNodes": -282,
"jsHeapTotalBytes": 5496832,
"scriptDurationMs": 84.783,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1747.2999999999956,
"styleRecalcs": 74,
"styleRecalcDurationMs": 32.374,
"layouts": 16,
"layoutDurationMs": 4.558,
"taskDurationMs": 753.546,
"heapDeltaBytes": 14845700,
"heapUsedBytes": 76490304,
"domNodes": -283,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 82.11500000000001,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1435.559000000012,
"styleRecalcs": 18,
"styleRecalcDurationMs": 32.06,
"layouts": 13,
"layoutDurationMs": 13.303000000000003,
"taskDurationMs": 960.786,
"heapDeltaBytes": -4005508,
"heapUsedBytes": 86665016,
"domNodes": 13673,
"jsHeapTotalBytes": 11010048,
"scriptDurationMs": 18.426,
"eventListeners": 2375,
"totalBlockingTimeMs": 131,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8279.106999999953,
"styleRecalcs": 251,
"styleRecalcDurationMs": 46.559999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4176.005,
"heapDeltaBytes": -1633448,
"heapUsedBytes": 74450268,
"domNodes": -269,
"jsHeapTotalBytes": 483328,
"scriptDurationMs": 995.5759999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8194.036000000096,
"styleRecalcs": 251,
"styleRecalcDurationMs": 40.766,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4050.908,
"heapDeltaBytes": 4883168,
"heapUsedBytes": 80610008,
"domNodes": -235,
"jsHeapTotalBytes": -303104,
"scriptDurationMs": 986.948,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 18382.514000000014,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17331.870000000003,
"heapDeltaBytes": -43238256,
"heapUsedBytes": 166091964,
"domNodes": -8312,
"jsHeapTotalBytes": -15142912,
"scriptDurationMs": 119.34700000000001,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-idle",
"durationMs": 18562.19199999998,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17697.052,
"heapDeltaBytes": -45320920,
"heapUsedBytes": 176794960,
"domNodes": -8312,
"jsHeapTotalBytes": -14618624,
"scriptDurationMs": 134.74099999999999,
"eventListeners": -16385,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21353.775999999983,
"styleRecalcs": 174,
"styleRecalcDurationMs": 20.498999999999988,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20780.958,
"heapDeltaBytes": -26061328,
"heapUsedBytes": 191387788,
"domNodes": -8312,
"jsHeapTotalBytes": -14131200,
"scriptDurationMs": 424.4169999999999,
"eventListeners": -16385,
"totalBlockingTimeMs": 192,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21544.426999999927,
"styleRecalcs": 176,
"styleRecalcDurationMs": 19.743999999999986,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20899.295000000002,
"heapDeltaBytes": -30533708,
"heapUsedBytes": 191603412,
"domNodes": -8312,
"jsHeapTotalBytes": -14950400,
"scriptDurationMs": 397.026,
"eventListeners": -16385,
"totalBlockingTimeMs": 98,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 116.87300000005507,
"styleRecalcs": 8,
"styleRecalcDurationMs": 13.88,
"layouts": 2,
"layoutDurationMs": 0.52,
"taskDurationMs": 75.23800000000001,
"heapDeltaBytes": 3052448,
"heapUsedBytes": 64297108,
"domNodes": 111,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 6.397999999999999,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 131.0280000000148,
"styleRecalcs": 9,
"styleRecalcDurationMs": 15.918000000000001,
"layouts": 2,
"layoutDurationMs": 1.4069999999999998,
"taskDurationMs": 88.799,
"heapDeltaBytes": 3032860,
"heapUsedBytes": 64132968,
"domNodes": 130,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 7.272999999999998,
"eventListeners": 27,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## ci/bump-pr-risk #15442 +/- ##
===================================================
- Coverage 82.06% 81.75% -0.32%
===================================================
Files 1887 1887
Lines 122840 107167 -15673
Branches 39638 33048 -6590
===================================================
- Hits 100814 87612 -13202
+ Misses 21522 19212 -2310
+ Partials 504 343 -161
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 6 finding(s).
| Severity | Count |
|---|---|
| 🟠 High | 1 |
| 🟡 Medium | 1 |
| 🟢 Low | 3 |
| ⚪ Nit | 1 |
Panel: 8/8 reviewers contributed findings.
Review found the opt-in comment overclaimed in three ways, each verified against the reusable workflow at the pinned SHA: - "can never fail a PR" holds for the hardcoded neutral conclusion but not for absence. The job's if: skips fork and Dependabot PRs, and upstream's publish-check gates on the kill switch even on a manual dispatch while the grade job deliberately does not, so a dispatched re-grade with the switch off relabels and publishes nothing. Marking the check required would block those PRs forever. - "the immutable record" reads as one per commit. Upstream only POSTs to /check-runs and never updates, so every re-grade of the same head sha appends another check; a commit can carry several with differing tiers. Commit-scoping also means two PRs on one sha get indistinguishable checks. - Publishing perturbs the next grade of the same sha: an existing check makes the rollup non-empty, so the grader's 120s not-yet-registered grace window stops applying to a re-grade, which can settle a poll early and floor the reversibility axis. Comment-only; the check_run: true opt-in is unchanged.
4dd404d to
197cc0b
Compare
Conflict was the pin only: the base rebased and moved the caller from github-workflows 6178233 to f8eec04, while this branch still carried the older pin under its `check_run: true` opt-in. Took the base's newer pin (f8eec04, 2026-08-20 > 6178233, 2026-08-17) and kept this branch's `check_run: true` plus the three-limits comment block, which sits outside the conflicted hunk. Re-verified every claim that block makes against pr-risk.yml at f8eec04 rather than carrying them forward on trust: - `check_run` and `check_name` are still declared inputs. - `publish-check` gates on `needs.gate.outputs.enabled == 'true' && inputs.check_run`, with no workflow_dispatch escape — while `grade` adds `|| github.event_name == 'workflow_dispatch'`. The "a dispatched re-grade under the kill switch relabels but publishes nothing" asymmetry holds. - The publish step is `gh api -X POST .../check-runs` with no update path and a hardcoded `conclusion: neutral`, so "appends, never revises" and "can never fail a PR" both still hold. - grade-targets.sh still sets `empty_deadline=$(date +%s) + 120` for the "checks have not registered yet" window, so the perturbs-the-next-grade note keeps its number. - The caller permissions block still covers the union upstream declares across gate/grade/publish-check (contents:read, issues:write, pull-requests:write, checks:write, actions:read, statuses:read).
197cc0b to
3cc39c6
Compare
…38-pr-risk-check-run # Conflicts: # .github/workflows/ci-pr-risk.yml
STACKED — merging lands on
ci/bump-pr-risk(owned by @cloud-code-bot, #15379), NOTmain.Summary
Opt the PR risk grader into publishing its verdict as a neutral
PR risk (advisory)Check Run, so a graded PR carries a commit-attached record of the tier and the per-axis reasoning behind it — not just the mutablerisk:*label. One input, no pin change.Changes
check_run: truein thewith:block of thepr-riskjob, plus the file-header sentence describing what the caller emits and a comment block recording the three limits of the artifact (see Limits).neutralupstream and the publish step always exits 0, so it cannot fail a PR even if a repo later marks it required.Why this is stacked instead of based on
maincheck_rundoes not exist on the pinmaincurrently carries (e4a8f7c, 2026-08-04) — that revision of the reusable workflow has nocheck_runinput and nopublish-checkjob at all. Addingcheck_run: trueon top of it would be an undefinedwith:input, which GitHub rejects at workflow startup, i.e. every PR would lose its grade entirely rather than gain a check. So this is based on #15379, which moves the pin to a revision where the input exists —6178233when this branch opened, andf8eec04after #15379 rebased onto a newer upstream SHA. The input is declared at both.Per the source request I did not bump the pin myself; #15379 owns that. When it moved
6178233→f8eec04, this branch took the base's pin verbatim in the merge and kept only thecheck_run: trueopt-in as its own diff.It is already working on this PR
pull_requestruns the caller from the merge ref, so this branch's own CI exercisedcheck_run: trueand published the check. This is the direct evidence that the input exists at the base pin and that the opt-in does what it claims:A
neutralconclusion renders asskippingingh pr checks, which is why it does not read as a new check in the rollup.Limits
Recorded in the workflow comment rather than left implicit, because each one was raised in review and each is real. All three are upstream-owned; a one-line caller opt-in cannot reach any of them.
if:skips fork and Dependabot PRs, and upstream'spublish-checkgates on the kill switch (needs.gate.outputs.enabled == 'true') even for a manual dispatch, whilegradedeliberately does not. So a dispatched re-grade withRISK_CONFIG {"enabled": false}still rewrites the label and publishes nothing — the dispute/backfill path is exactly the one that can produce no record. Never markPR risk (advisory)required in branch protection: the skipped PRs would block forever on a check that is never created.POSTs to/check-runs; it never looks up or updates an existing run of the same name. Every re-grade of the same head sha (reopen,ready_for_reviewafteropened, a re-run, a dispatch) appends another check, so a commit can carry several with differing tiers and nothing marking which is current. The check is also commit-scoped while the grade is PR-scoped, so two PRs sharing a head sha get indistinguishable checks. Read the newest; the run log stays the tiebreak.checks_statenon-null, so the grader's 120s "checks have not registered yet" grace window stops applying to a re-grade, which can settle a poll early and floor the reversibility axis. Bounded to re-grades of the same sha (a fresh push has no prior check on its commit), and advisory throughout.Review Focus
checks: writeis already in the caller's block — every caller on this pin owes that grant whether or not it opts in, because GitHub validates the nestedpublish-checkjob's declared permissions at startup regardless of itsif:. Verified the caller block is a superset of the union of all three upstream jobs at the current pinf8eec04(gate{};gradecontents:read/issues:write/pull-requests:write/checks:read/actions:read/statuses:read;publish-checkchecks:write/contents:read/pull-requests:read).neutral, and the grader's rollup ladder isFAILURE → PENDING → SUCCESS → NEUTRAL → null; a neutral context can only turn anullrollup intoNEUTRAL, and the reversibility axis tests$checks == null or $checks != "SUCCESS", which treats those two identically. So a re-grade of the same head SHA cannot grade worse on that axis because of the check this PR causes to be published. The check is not self-excluded (self-exclusion keys oncheckSuite.workflowRun.databaseIdand a REST-created check run has noworkflowRun), which is what makes the third limit above bite — the tier is unaffected by the ladder, but the timing of the poll loop is not.ci/bump-pr-riskonto a newer upstream SHA, this branch needs a rebase onto the new base before it can merge.check_nameis left at its upstream default,PR risk (advisory).Verification
Workflow-only diff, no
src/touched, so the Vitest/Playwright suites are not implicated and were not run — that is stated rather than papered over.actionlint .github/workflows/ci-pr-risk.yml→ 0 findings.withresolves toworkflows_ref,enabled,pr_number,pr_numbers,check_run: true; theuses:ref andworkflows_refare the identical 40-hex SHAf8eec04e7923f4719d5b7dbc342e5c15580e22ba(the lock-step pin contract the reusable workflow enforces).main'se4a8f7c, declaredtype: boolean, default: falseat6178233, and still declared at the current base pinf8eec04.publish-check'sif:and its POST-only publish loop inpr-risk.yml, theempty_deadline/settled_oncepoll loop inscripts/pr-risk/grade-targets.sh.What this does not do
risk:*label; none has an advisory Check Run, and none gains one retroactively — each picks one up on its next push, reopen, or ready-for-review event. A manualworkflow_dispatchwithpr_numbersis the backfill lever (subject to the kill-switch limit above).Provenance
check_run: trueand identicaluses:/workflows_refpins; upstream input-existence check at three pins (absent atmain'se4a8f7c, present at6178233and at the current base pinf8eec04); end-to-end confirmation that the neutralPR risk (advisory)check (Risk: R3) published on this PR's own head commit; each documented limit read out of the pinned upstream source (publish-check'sif:and POST-only publish loop; theempty_deadline/settled_oncepoll loop ingrade-targets.sh). After merging the rebased base, every one of those claims was re-read againstf8eec04rather than carried forward on trust —check_run/check_namestill declared;publish-checkstillneeds.gate.outputs.enabled == 'true' && inputs.check_runwith noworkflow_dispatchescape whilegradekeeps one; the publish step still POST-only with a hardcodedneutral;grade-targets.shstillempty_deadline=$(date +%s) + 120; the caller permissions block still a superset of the upstream union.actionlintre-run clean on the merged file. Repo test suites not run — nosrc/change.main, because the input does not exist atmain's current pin (see above). All six review findings were adjudicated against the pinned upstream source: the High severity one (undefined input → startup failure) is refuted by the check this PR already published; the other five are confirmed but upstream-owned and unfixable from a caller, so they are recorded as comments at the opt-in site instead of being coded around. No follow-up tickets filed — each is advisory-only and belongs to Comfy-Org/github-workflows, not this repo. The base branch was later force-pushed onto a newer upstream SHA (the "rebase hazard" this body called out), which conflicted this branch on the pin lines; resolved by taking the base'sf8eec04and keeping this branch'scheck_run: trueand comment block, so the diff against the base is unchanged in substance.