@plainconceptsplatform/workflows 0.19.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog-installation.js +12 -1
- package/dist/stack-defaults.js +16 -16
- package/dist/worker-env.js +14 -0
- package/loops/actions/add-issue-labels/action.yml +50 -50
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/apply-agent-bundle/action.yml +24 -24
- package/loops/actions/apply-agent-comments/action.yml +42 -42
- package/loops/actions/apply-agent-labels/action.yml +55 -55
- package/loops/actions/apply-agent-output/action.yml +108 -108
- package/loops/actions/assess-blast-radius/action.yml +148 -0
- package/loops/actions/assess-blast-radius/assess-blast-radius.sh +138 -0
- package/loops/actions/classify-route/action.yml +100 -100
- package/loops/actions/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-agent-issues/action.yml +52 -52
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- package/loops/actions/housekeeping/action.yml +55 -2
- package/loops/actions/link-pr-to-issue/action.yml +40 -40
- package/loops/actions/list-open-issues/action.yml +33 -33
- package/loops/actions/load-issue-context/action.yml +45 -45
- package/loops/actions/merge-agent-pr/action.yml +49 -49
- package/loops/actions/push-agent-branch/action.yml +45 -45
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- package/loops/actions/validate-merge-gate-output/action.yml +62 -40
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +147 -31
- package/loops/actions/validate-refine-output/action.yml +48 -44
- package/loops/actions/validate-refine-output/validate-refine-output.sh +15 -4
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-composite-actions/action.yml +9 -9
- package/loops/actions/verify-refine-output/action.yml +9 -9
- package/loops/actions/verify-refine-output/verify-refine-output.sh +6 -1
- package/loops/actions/verify-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +51 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +329 -29
- package/loops/scripts/compile-agent-workflows.mjs +331 -331
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
- package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/opencode/opencode.ci.json +55 -49
- package/loops/templates/opencode/opencode.ci.json.md +59 -49
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-merge-gate.md +367 -148
- package/loops/workflows/agent-refine.md +60 -16
- package/loops/workflows/authorize-bot-work.yml +105 -105
- package/loops/workflows/shared/opencode-ci.md +206 -206
- package/loops/workflows/shared/platform-defaults.md +19 -19
- package/package.json +1 -1
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-merge-gate.md. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
3
|
env:
|
|
4
4
|
VERIFY_COMMANDS: ""
|
|
5
|
-
REPO_RULES: "
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
5
|
+
REPO_RULES: "Review the selected bot pull request for defects and report what you verified. Do not decide the outcome: the workflow computes it from your report and from facts it measured before you ran."
|
|
6
|
+
# Where to look first, not what to escalate on. This list used to be the check that decided
|
|
7
|
+
# whether a machine merged without a human, and it decided by category: the prompt told the
|
|
8
|
+
# agent that a match "is not a defect, it is a reason this pull request needs a person". In a
|
|
9
|
+
# layered application every feature PR touches an entity or a contract, so the gate escalated
|
|
10
|
+
# almost everything and, measured on a consumer, auto-merged 31% of terminal verdicts while
|
|
11
|
+
# finding zero defects. The areas are still worth naming; they are now the review pass's
|
|
12
|
+
# attention list. What decides is evidence, in the decision table the validator owns.
|
|
10
13
|
# One line: gh-aw joins a multi-line env value onto a single line when it compiles the lock.
|
|
11
14
|
RISK_INDICATORS: "Any diff touching authentication, authorization or session handling. Any change to a calculation or pricing engine, or to code handling money. Any database migration, or a change to an entity or schema. Any change to an audit or event log, or anything that could break its continuity. Any change to a public API contract or a shared library other repositories consume."
|
|
12
15
|
# Paths a bot may change but never merge on its own: an extended regular expression matched
|
|
@@ -16,9 +19,42 @@ env:
|
|
|
16
19
|
# list protects nothing and reports nothing. A match holds the merge for a human; it does not
|
|
17
20
|
# stop the agent repairing failed CI on the same files.
|
|
18
21
|
PROTECTED_PATHS: '^(\.|AGENTS\.md$|ARCHITECTURE\.md$|opencode\.jsonc$|package\.json$|pnpm-lock\.yaml$|Directory\.Packages\.props$|global\.json$)'
|
|
22
|
+
# Paths whose change needs the person who owns them. A match forces OWNER REVIEW REQUIRED and
|
|
23
|
+
# sets blast radius high on its own, whatever the diff's size. CODEOWNERS names who is asked
|
|
24
|
+
# when the repository has that file; it is never a prerequisite, because a repository without
|
|
25
|
+
# one must still be able to protect its auth and its infrastructure.
|
|
26
|
+
#
|
|
27
|
+
# Matches a path segment or a file stem, in both spellings, because the same default has to
|
|
28
|
+
# work for `src/auth/`, `src/Api/Identity/` and `AuthEndpoints.cs`. The lowercase-only,
|
|
29
|
+
# directory-only version this replaced matched nothing at all in a .NET consumer: replayed
|
|
30
|
+
# against that repository's last eighteen gated pull requests it caught none of them, while
|
|
31
|
+
# this one catches exactly three and they are the three that deserved an owner (a database
|
|
32
|
+
# migration, a change to the platform role definitions, and a downstream token service).
|
|
33
|
+
OWNER_PATHS: '(^|/)([Aa]uth|[Aa]uthn|[Aa]uthz|[Aa]uthentication|[Aa]uthorization|[Ii]dentity|[Ss]ecurity|[Ss]ecrets?|[Mm]igrations|[Ii]nfra|terraform|helm|k8s|deploy)(/|[A-Z][A-Za-z]*\.[a-z]+$)'
|
|
34
|
+
# Paths worth a second look that do not, alone, need a person. A match raises the floor to
|
|
35
|
+
# medium, and medium with acceptable recoverability still auto-merges. This is the line that
|
|
36
|
+
# separates "look here" from "stop here", which the old RISK_INDICATORS list could not.
|
|
37
|
+
SENSITIVE_PATHS: '(^|/)([Dd]omain|entities|[Cc]ontracts)/'
|
|
38
|
+
# Diff shape. Size and spread are the honest deterministic signal for a change that touches no
|
|
39
|
+
# path a regex would name: the one pull request in the measured sample that genuinely wanted an
|
|
40
|
+
# owner matched no sensitive path and was identified by 29 files and ~1600 lines across five
|
|
41
|
+
# architectural layers.
|
|
42
|
+
BLAST_HIGH_FILES: "20"
|
|
43
|
+
BLAST_HIGH_LINES: "800"
|
|
44
|
+
BLAST_MEDIUM_FILES: "5"
|
|
45
|
+
BLAST_MEDIUM_LINES: "200"
|
|
46
|
+
# Agent confidence below which the pull request goes to a human. The agent reports the number;
|
|
47
|
+
# this decides what it means.
|
|
48
|
+
CONFIDENCE_THRESHOLD: "0.8"
|
|
19
49
|
WORKING_LABEL: bot-working
|
|
20
50
|
IMPLEMENT_LABEL: implement
|
|
21
51
|
REVIEW_LABEL: review
|
|
52
|
+
# Sits alongside `review`, never instead of it, so every board query that already asks for
|
|
53
|
+
# `review` keeps working. What it adds is the distinction the single label could not carry:
|
|
54
|
+
# `owner-review` says a named area changed, `blocked` says the machine could not proceed rather
|
|
55
|
+
# than chose not to. The belt does not retry a blocked pull request.
|
|
56
|
+
OWNER_REVIEW_LABEL: owner-review
|
|
57
|
+
BLOCKED_LABEL: blocked
|
|
22
58
|
# Marks a park the machine caused — a crash, a timeout, an empty output — as opposed to one it
|
|
23
59
|
# decided on. The janitor retries these after a while and never touches a decision park, because
|
|
24
60
|
# re-running a decision produces the same decision. Created idempotently where it is applied.
|
|
@@ -28,6 +64,13 @@ env:
|
|
|
28
64
|
ATTEMPT_MARKER: "<!-- agent-merge-gate-attempt -->"
|
|
29
65
|
MAX_ATTEMPTS: "6"
|
|
30
66
|
PARK_AT_ATTEMPT: "5"
|
|
67
|
+
# A smaller budget for the one failure that repeating does not fix. A crashed or timed-out run
|
|
68
|
+
# is a machine failure and worth repeating; a run that finished and handed back a report the
|
|
69
|
+
# validator could not read is a formatting problem, and the third attempt looks like the first.
|
|
70
|
+
# The cost is not hypothetical: every retry is a fresh agent run with this worker timeout, and
|
|
71
|
+
# `call-merge-gate` holds the repo-wide `merge-belt` slot while it runs, so five attempts on
|
|
72
|
+
# one unusable report can keep every other bot pull request in the repository waiting.
|
|
73
|
+
PARK_AT_UNUSABLE_OUTPUT: "2"
|
|
31
74
|
ISSUE_CONTEXT_PATH: /tmp/gh-aw/agent/issue-context.json
|
|
32
75
|
GH_AW_ALLOWED_BOTS: "platform-devbox[bot],github-actions[bot]"
|
|
33
76
|
GIT_AUTHOR_NAME: "github-actions[bot]"
|
|
@@ -137,45 +180,53 @@ jobs:
|
|
|
137
180
|
done
|
|
138
181
|
echo "review_blocked=$([ "$decision" = 'CHANGES_REQUESTED' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
139
182
|
|
|
183
|
+
# Rung 4b. Everything the merge decision needs that a shell can establish, measured once,
|
|
184
|
+
# before any model reads the diff. The job kept its name and its `requires_review` and
|
|
185
|
+
# `holds_review` outputs because eight guards across this file and the route matrix read them;
|
|
186
|
+
# what it gained is the blast radius those guards never had.
|
|
140
187
|
protected_changes:
|
|
141
188
|
needs: subject
|
|
142
189
|
if: needs.subject.outputs.found == 'true'
|
|
143
190
|
runs-on: agents-arc
|
|
144
191
|
permissions:
|
|
192
|
+
contents: read
|
|
145
193
|
pull-requests: read
|
|
146
194
|
outputs:
|
|
147
|
-
requires_review: ${{ steps.
|
|
148
|
-
files: ${{ steps.
|
|
195
|
+
requires_review: ${{ steps.blast.outputs.requires_review }}
|
|
196
|
+
files: ${{ steps.blast.outputs.files }}
|
|
197
|
+
level: ${{ steps.blast.outputs.level }}
|
|
198
|
+
signals: ${{ steps.blast.outputs.signals }}
|
|
199
|
+
owner_hits: ${{ steps.blast.outputs.owner_hits }}
|
|
200
|
+
sensitive_hits: ${{ steps.blast.outputs.sensitive_hits }}
|
|
201
|
+
required_owners: ${{ steps.blast.outputs.required_owners }}
|
|
202
|
+
files_changed: ${{ steps.blast.outputs.files_changed }}
|
|
203
|
+
lines_changed: ${{ steps.blast.outputs.lines_changed }}
|
|
204
|
+
owner_hit: ${{ steps.blast.outputs.owner_hit }}
|
|
205
|
+
sensitive_hit: ${{ steps.blast.outputs.sensitive_hit }}
|
|
149
206
|
# The decision, computed once. A protected path holds the merge for a human, but it must
|
|
150
207
|
# not stop the agent repairing failed CI on those same files: blocking there strands the
|
|
151
208
|
# pull request with nobody able to fix it. That pair of conditions used to be restated at
|
|
152
209
|
# eight call sites, five of them steps of one job, and the trap table documents it because
|
|
153
210
|
# it has already been got wrong. `holds_review` is the only place it is decided now.
|
|
154
|
-
holds_review: ${{ steps.
|
|
211
|
+
holds_review: ${{ steps.blast.outputs.requires_review == 'true' && needs.subject.outputs.conclusion != 'failure' }}
|
|
155
212
|
steps:
|
|
156
|
-
- name:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
protected
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
echo EOF
|
|
174
|
-
} >> "$GITHUB_OUTPUT"
|
|
175
|
-
else
|
|
176
|
-
echo "requires_review=false" >> "$GITHUB_OUTPUT"
|
|
177
|
-
echo "files=" >> "$GITHUB_OUTPUT"
|
|
178
|
-
fi
|
|
213
|
+
- name: Checkout workflow actions
|
|
214
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
215
|
+
with:
|
|
216
|
+
persist-credentials: false
|
|
217
|
+
- name: Assess blast radius
|
|
218
|
+
id: blast
|
|
219
|
+
uses: ./.github/actions/assess-blast-radius
|
|
220
|
+
with:
|
|
221
|
+
token: ${{ github.token }}
|
|
222
|
+
pr-number: ${{ needs.subject.outputs.pr }}
|
|
223
|
+
protected-paths: ${{ env.PROTECTED_PATHS }}
|
|
224
|
+
owner-paths: ${{ env.OWNER_PATHS }}
|
|
225
|
+
sensitive-paths: ${{ env.SENSITIVE_PATHS }}
|
|
226
|
+
high-files: ${{ env.BLAST_HIGH_FILES }}
|
|
227
|
+
high-lines: ${{ env.BLAST_HIGH_LINES }}
|
|
228
|
+
medium-files: ${{ env.BLAST_MEDIUM_FILES }}
|
|
229
|
+
medium-lines: ${{ env.BLAST_MEDIUM_LINES }}
|
|
179
230
|
|
|
180
231
|
review_required:
|
|
181
232
|
needs: [subject, protected_changes]
|
|
@@ -210,13 +261,15 @@ jobs:
|
|
|
210
261
|
token: ${{ steps.app-token.outputs.token }}
|
|
211
262
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
212
263
|
labels: ${{ env.WORKING_LABEL }}
|
|
213
|
-
- name: Flag
|
|
264
|
+
- name: Flag owner review
|
|
214
265
|
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
215
266
|
uses: ./.github/actions/add-issue-labels
|
|
216
267
|
with:
|
|
217
268
|
token: ${{ steps.app-token.outputs.token }}
|
|
218
269
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
219
|
-
labels:
|
|
270
|
+
labels: |-
|
|
271
|
+
${{ env.REVIEW_LABEL }}
|
|
272
|
+
${{ env.OWNER_REVIEW_LABEL }}
|
|
220
273
|
- name: Explain the merge hold
|
|
221
274
|
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
222
275
|
uses: ./.github/actions/create-issue-comment
|
|
@@ -226,12 +279,15 @@ jobs:
|
|
|
226
279
|
body: |
|
|
227
280
|
${{ env.GATE_MARKER }}
|
|
228
281
|
PR #${{ needs.subject.outputs.pr }} changes protected files and cannot be auto-merged.
|
|
229
|
-
The `review`
|
|
282
|
+
The `review` and `owner-review` labels are set: the person who owns these files
|
|
283
|
+
decides, and merges.
|
|
230
284
|
|
|
231
285
|
Protected files:
|
|
232
286
|
${{ needs.protected_changes.outputs.files }}
|
|
233
287
|
|
|
234
|
-
|
|
288
|
+
Required owners: ${{ needs.protected_changes.outputs.required_owners || 'none configured' }}
|
|
289
|
+
|
|
290
|
+
**Verdict:** owner-review
|
|
235
291
|
|
|
236
292
|
reserve:
|
|
237
293
|
needs: subject
|
|
@@ -280,7 +336,7 @@ jobs:
|
|
|
280
336
|
Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || 'CI failed.' }}
|
|
281
337
|
Bot is working on fixing it.
|
|
282
338
|
validate_output:
|
|
283
|
-
needs: [activation, subject, agent, safe_outputs]
|
|
339
|
+
needs: [activation, subject, protected_changes, agent, safe_outputs]
|
|
284
340
|
if: >
|
|
285
341
|
always() &&
|
|
286
342
|
needs.agent.result == 'success' &&
|
|
@@ -301,20 +357,32 @@ jobs:
|
|
|
301
357
|
uses: ./.github/actions/download-agent-output
|
|
302
358
|
with:
|
|
303
359
|
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
304
|
-
|
|
360
|
+
# Where the decision is made. The agent contributed evidence; these inputs are the facts
|
|
361
|
+
# protected_changes measured before it ran. Neither half can produce a disposition alone.
|
|
362
|
+
- name: Compute the merge-gate disposition
|
|
305
363
|
id: validate
|
|
306
364
|
uses: ./.github/actions/validate-merge-gate-output
|
|
307
365
|
with:
|
|
308
366
|
output-file: ${{ steps.output.outputs.output-file }}
|
|
309
367
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
310
368
|
ci-conclusion: ${{ needs.subject.outputs.conclusion }}
|
|
369
|
+
blast-level: ${{ needs.protected_changes.outputs.level }}
|
|
370
|
+
protected-hit: ${{ needs.protected_changes.outputs.requires_review }}
|
|
371
|
+
owner-hit: ${{ needs.protected_changes.outputs.owner_hit }}
|
|
372
|
+
confidence-threshold: ${{ env.CONFIDENCE_THRESHOLD }}
|
|
311
373
|
conclude:
|
|
312
374
|
needs: [activation, subject, protected_changes, agent, safe_outputs, validate_output]
|
|
375
|
+
# `protected_changes.result == 'success'` is stated rather than relied on. GitHub skips a job
|
|
376
|
+
# whose needs failed, so this condition was never reached on that path, but every clause in
|
|
377
|
+
# it read as safe on a job that never ran: `requires_review` is '' when protected_changes
|
|
378
|
+
# fails, and '' != 'true'. A guard whose safety comes from somewhere else is a guard that
|
|
379
|
+
# stops working the moment someone adds always() to this job.
|
|
313
380
|
if: >
|
|
314
381
|
needs.agent.result == 'success' &&
|
|
315
382
|
needs.safe_outputs.result == 'success' &&
|
|
383
|
+
needs.protected_changes.result == 'success' &&
|
|
316
384
|
needs.validate_output.outputs.valid == 'true' &&
|
|
317
|
-
(needs.protected_changes.outputs.requires_review != 'true' || needs.validate_output.outputs.outcome != 'merge')
|
|
385
|
+
(needs.protected_changes.outputs.requires_review != 'true' || needs.validate_output.outputs.outcome != 'auto-merge')
|
|
318
386
|
runs-on: agents-arc
|
|
319
387
|
permissions:
|
|
320
388
|
contents: write
|
|
@@ -351,17 +419,36 @@ jobs:
|
|
|
351
419
|
# lifecycle is; this is what a reviewer opening the pull request sees. Carrying the
|
|
352
420
|
# marker and the Verdict line makes the router's verdict detection independent of
|
|
353
421
|
# whether the model remembered the marker.
|
|
354
|
-
|
|
422
|
+
# One block so the reader sees the whole disposition at once instead of reconstructing it
|
|
423
|
+
# from a word. Everything on it was either measured before the agent ran or computed from
|
|
424
|
+
# what the agent proved; nothing here is the model's own summary of its mood.
|
|
425
|
+
- name: Show the disposition on the pull request
|
|
355
426
|
uses: ./.github/actions/create-issue-comment
|
|
356
427
|
with:
|
|
357
428
|
token: ${{ github.token }}
|
|
358
429
|
issue-number: ${{ needs.subject.outputs.pr }}
|
|
359
430
|
body: |
|
|
360
431
|
${{ env.GATE_MARKER }}
|
|
361
|
-
**Verdict:** ${{ needs.validate_output.outputs.outcome }}
|
|
362
|
-
|
|
432
|
+
**Verdict:** ${{ needs.validate_output.outputs.outcome }}
|
|
433
|
+
|
|
434
|
+
| | |
|
|
435
|
+
|---|---|
|
|
436
|
+
| Disposition | `${{ needs.validate_output.outputs.outcome }}` |
|
|
437
|
+
| CI | ${{ needs.subject.outputs.conclusion }} |
|
|
438
|
+
| Blast radius | ${{ needs.protected_changes.outputs.level }} |
|
|
439
|
+
| Files / lines | ${{ needs.protected_changes.outputs.files_changed }} / ${{ needs.protected_changes.outputs.lines_changed }} |
|
|
440
|
+
| Protected paths | ${{ needs.protected_changes.outputs.requires_review == 'true' && 'yes' || 'no' }} |
|
|
441
|
+
| Owner paths | ${{ needs.protected_changes.outputs.owner_hit == 'true' && 'yes' || 'no' }} |
|
|
442
|
+
| Required owners | ${{ needs.protected_changes.outputs.required_owners || 'none configured' }} |
|
|
443
|
+
|
|
444
|
+
Why this blast radius:
|
|
445
|
+
```
|
|
446
|
+
${{ needs.protected_changes.outputs.signals }}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Findings and verification on the linked issue: #${{ needs.subject.outputs.issue }}. [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
363
450
|
- name: Merge approved pull request
|
|
364
|
-
if: needs.validate_output.outputs.outcome == 'merge'
|
|
451
|
+
if: needs.validate_output.outputs.outcome == 'auto-merge'
|
|
365
452
|
env:
|
|
366
453
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
367
454
|
REPO: ${{ github.repository }}
|
|
@@ -377,24 +464,50 @@ jobs:
|
|
|
377
464
|
token: ${{ steps.app-token.outputs.token }}
|
|
378
465
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
379
466
|
labels: ${{ env.WORKING_LABEL }}
|
|
380
|
-
|
|
381
|
-
|
|
467
|
+
# `review` goes on for all three parked dispositions, so every board query and every
|
|
468
|
+
# authorize-bot-work handoff that already reads it keeps working. The second label is what
|
|
469
|
+
# tells a person which kind of parking this is.
|
|
470
|
+
- name: Flag a parked outcome
|
|
471
|
+
if: contains(fromJson('["human-review","owner-review","blocked"]'), needs.validate_output.outputs.outcome)
|
|
382
472
|
uses: ./.github/actions/add-issue-labels
|
|
383
473
|
with:
|
|
384
474
|
token: ${{ steps.app-token.outputs.token }}
|
|
385
475
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
386
|
-
labels:
|
|
476
|
+
labels: |-
|
|
477
|
+
${{ env.REVIEW_LABEL }}
|
|
478
|
+
${{ needs.validate_output.outputs.outcome == 'owner-review' && env.OWNER_REVIEW_LABEL || '' }}
|
|
479
|
+
${{ needs.validate_output.outputs.outcome == 'blocked' && env.BLOCKED_LABEL || '' }}
|
|
480
|
+
# Asking the owner is best effort on purpose. A CODEOWNERS entry can name a team this App
|
|
481
|
+
# cannot request, and a failed request must not strand a pull request whose label and
|
|
482
|
+
# comment already say who is wanted.
|
|
483
|
+
- name: Request the owners named by CODEOWNERS
|
|
484
|
+
if: needs.validate_output.outputs.outcome == 'owner-review' && needs.protected_changes.outputs.required_owners != ''
|
|
485
|
+
continue-on-error: true
|
|
486
|
+
env:
|
|
487
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
488
|
+
REPO: ${{ github.repository }}
|
|
489
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
490
|
+
OWNERS: ${{ needs.protected_changes.outputs.required_owners }}
|
|
491
|
+
run: |
|
|
492
|
+
set -euo pipefail
|
|
493
|
+
for owner in $OWNERS; do
|
|
494
|
+
case "$owner" in
|
|
495
|
+
*@*) continue ;; # an email address is not a reviewer
|
|
496
|
+
@*/*) gh pr edit "$PR" --repo "$REPO" --add-reviewer "${owner#@}" || true ;;
|
|
497
|
+
@*) gh pr edit "$PR" --repo "$REPO" --add-reviewer "${owner#@}" || true ;;
|
|
498
|
+
esac
|
|
499
|
+
done
|
|
387
500
|
# The reservation only. The pull request is still open and still waiting, so pr-pending
|
|
388
501
|
# stays until the merge path below retires it.
|
|
389
|
-
- name: Release
|
|
390
|
-
if: needs.validate_output.outputs.outcome
|
|
502
|
+
- name: Release a parked outcome
|
|
503
|
+
if: contains(fromJson('["human-review","owner-review","blocked"]'), needs.validate_output.outputs.outcome)
|
|
391
504
|
uses: ./.github/actions/remove-issue-labels
|
|
392
505
|
with:
|
|
393
506
|
token: ${{ steps.app-token.outputs.token }}
|
|
394
507
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
395
508
|
labels: ${{ env.WORKING_LABEL }}
|
|
396
509
|
- name: Clear merged issue labels
|
|
397
|
-
if: needs.validate_output.outputs.outcome == 'merge'
|
|
510
|
+
if: needs.validate_output.outputs.outcome == 'auto-merge'
|
|
398
511
|
uses: ./.github/actions/remove-issue-labels
|
|
399
512
|
with:
|
|
400
513
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -403,6 +516,8 @@ jobs:
|
|
|
403
516
|
${{ env.IMPLEMENT_LABEL }}
|
|
404
517
|
${{ env.WORKING_LABEL }}
|
|
405
518
|
${{ env.REVIEW_LABEL }}
|
|
519
|
+
${{ env.OWNER_REVIEW_LABEL }}
|
|
520
|
+
${{ env.BLOCKED_LABEL }}
|
|
406
521
|
${{ env.PR_PENDING_LABEL }}
|
|
407
522
|
incomplete:
|
|
408
523
|
needs: [subject, protected_changes, agent, safe_outputs, validate_output]
|
|
@@ -439,8 +554,37 @@ jobs:
|
|
|
439
554
|
# attempts_so_far is a workflow_call input and arrives as '' when the caller passes an
|
|
440
555
|
# empty expression, declared default or not; fromJson('') is a hard failure, so the empty
|
|
441
556
|
# case reads as 0.
|
|
557
|
+
#
|
|
558
|
+
# Which budget applies is decided once, here, rather than restated in each step condition:
|
|
559
|
+
# the same pair of conditions spread across four `if:` expressions is what the trap table
|
|
560
|
+
# already records going wrong for the protected-files hold.
|
|
561
|
+
- name: Choose the budget this failure gets
|
|
562
|
+
id: budget
|
|
563
|
+
env:
|
|
564
|
+
ATTEMPTS: ${{ inputs.attempts_so_far || '0' }}
|
|
565
|
+
AGENT_RESULT: ${{ needs.agent.result }}
|
|
566
|
+
SAFE_RESULT: ${{ needs.safe_outputs.result }}
|
|
567
|
+
OUTPUT_VALID: ${{ needs.validate_output.outputs.valid }}
|
|
568
|
+
PARK_AT_ATTEMPT: ${{ env.PARK_AT_ATTEMPT }}
|
|
569
|
+
PARK_AT_UNUSABLE_OUTPUT: ${{ env.PARK_AT_UNUSABLE_OUTPUT }}
|
|
570
|
+
run: |
|
|
571
|
+
set -euo pipefail
|
|
572
|
+
attempts=${ATTEMPTS:-0}
|
|
573
|
+
# The agent ran, published, and produced something the validator refused. Repeating
|
|
574
|
+
# that reproduces it; a person reading the comment costs less than three more runs
|
|
575
|
+
# holding the merge belt.
|
|
576
|
+
if [ "$AGENT_RESULT" = success ] && [ "$SAFE_RESULT" = success ] && [ "$OUTPUT_VALID" != true ]; then
|
|
577
|
+
threshold="$PARK_AT_UNUSABLE_OUTPUT"
|
|
578
|
+
kind=unusable
|
|
579
|
+
else
|
|
580
|
+
threshold="$PARK_AT_ATTEMPT"
|
|
581
|
+
kind=machine
|
|
582
|
+
fi
|
|
583
|
+
echo "kind=$kind" >> "$GITHUB_OUTPUT"
|
|
584
|
+
echo "threshold=$threshold" >> "$GITHUB_OUTPUT"
|
|
585
|
+
echo "park=$([ "$attempts" -ge "$threshold" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
442
586
|
- name: Report the failed attempt
|
|
443
|
-
if:
|
|
587
|
+
if: steps.budget.outputs.park == 'false'
|
|
444
588
|
uses: ./.github/actions/create-issue-comment
|
|
445
589
|
with:
|
|
446
590
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -448,28 +592,53 @@ jobs:
|
|
|
448
592
|
body: |
|
|
449
593
|
${{ env.ATTEMPT_MARKER }}
|
|
450
594
|
Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ env.MAX_ATTEMPTS }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
|
|
451
|
-
The issue keeps `implement`; the merge belt will retry.
|
|
595
|
+
This failure parks at ${{ steps.budget.outputs.threshold }}. The issue keeps `implement`; the merge belt will retry.
|
|
452
596
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
453
597
|
- name: Report the exhausted attempt budget
|
|
454
|
-
if:
|
|
598
|
+
if: steps.budget.outputs.park == 'true' && steps.budget.outputs.kind == 'machine'
|
|
455
599
|
uses: ./.github/actions/create-issue-comment
|
|
456
600
|
with:
|
|
457
601
|
token: ${{ steps.app-token.outputs.token }}
|
|
458
602
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
459
603
|
body: |
|
|
460
604
|
${{ env.ATTEMPT_MARKER }}
|
|
461
|
-
Attempt ${{ inputs.attempts_so_far || '0' }} of ${{
|
|
605
|
+
Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ steps.budget.outputs.threshold }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
|
|
462
606
|
The attempt budget for this CI verdict is exhausted. The review label is set: a human must take over.
|
|
463
607
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
608
|
+
# A verdict, not an attempt record, and this is the point of the whole split. The belt
|
|
609
|
+
# bounds its own retries by counting attempt comments to MAX_GATE_ATTEMPTS, so a worker
|
|
610
|
+
# that merely stopped parking would still be dispatched to the cap: the budget above would
|
|
611
|
+
# have saved nothing. A comment carrying the gate marker and a Verdict line is the contract
|
|
612
|
+
# the belt already respects -- it parks the pull request until a new commit moves the head
|
|
613
|
+
# past it -- and an unusable report is a decision, not a failure to repeat. It carries no
|
|
614
|
+
# attempt marker, so it is counted once, as what it is.
|
|
615
|
+
- name: Record an unusable report as a decision
|
|
616
|
+
if: steps.budget.outputs.park == 'true' && steps.budget.outputs.kind == 'unusable'
|
|
617
|
+
uses: ./.github/actions/create-issue-comment
|
|
618
|
+
with:
|
|
619
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
620
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
621
|
+
body: |
|
|
622
|
+
${{ env.GATE_MARKER }}
|
|
623
|
+
The agent finished on PR #${{ needs.subject.outputs.pr }} but its report could not be
|
|
624
|
+
read, ${{ steps.budget.outputs.threshold }} times on this head. Repeating it reproduces
|
|
625
|
+
it, so the belt stops here rather than spending the rest of the budget holding the
|
|
626
|
+
merge slot. The run log holds the output the gate refused.
|
|
627
|
+
|
|
628
|
+
**Verdict:** human-review
|
|
629
|
+
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
630
|
+
# `stalled` means a park the machine caused, and the janitor retries those. An unusable
|
|
631
|
+
# report is a park the machine caused and retrying reproduces it, so it gets `review`
|
|
632
|
+
# alone: the janitor's own rule is retry a failure, report a decision.
|
|
464
633
|
- name: Park the issue for a human
|
|
465
|
-
if:
|
|
634
|
+
if: steps.budget.outputs.park == 'true'
|
|
466
635
|
uses: ./.github/actions/add-issue-labels
|
|
467
636
|
with:
|
|
468
637
|
token: ${{ steps.app-token.outputs.token }}
|
|
469
638
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
470
639
|
labels: |-
|
|
471
640
|
${{ env.REVIEW_LABEL }}
|
|
472
|
-
${{ env.STALLED_LABEL }}
|
|
641
|
+
${{ steps.budget.outputs.kind == 'machine' && env.STALLED_LABEL || '' }}
|
|
473
642
|
# The reservation only. A failed attempt does not close the pull request, so pr-pending
|
|
474
643
|
# is still true and the board should keep saying so.
|
|
475
644
|
- name: Release the issue
|
|
@@ -622,30 +791,31 @@ timeout-minutes: 120
|
|
|
622
791
|
`push_to_pull_request_branch` tool's own description recommends rebasing; in this
|
|
623
792
|
repository that advice is wrong. Merge, commit, and let the workflow push.
|
|
624
793
|
|
|
625
|
-
2. Read `${{ env.ISSUE_CONTEXT_PATH }}`. It contains the issue body and its discussion.
|
|
626
|
-
|
|
627
|
-
|
|
794
|
+
2. Read `${{ env.ISSUE_CONTEXT_PATH }}`. It contains the issue body and its discussion. The
|
|
795
|
+
acceptance criteria there define what this implementation had to satisfy, and step 5c asks
|
|
796
|
+
you to check the diff against them.
|
|
628
797
|
|
|
629
798
|
3. Branch on the conclusion.
|
|
630
799
|
|
|
631
|
-
|
|
800
|
+
You do not choose the outcome. The workflow computes it from your report and from facts it
|
|
801
|
+
measured before you started. Two of those facts are already decided and you cannot argue with
|
|
802
|
+
either: CI concluded what it concluded, and the blast radius below was measured from the
|
|
803
|
+
changed paths and the diff shape.
|
|
632
804
|
|
|
633
|
-
**
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
to step 8 (merge verdict).
|
|
805
|
+
**Measured blast radius: `${{ needs.protected_changes.outputs.level }}`**
|
|
806
|
+
(${{ needs.protected_changes.outputs.files_changed }} files,
|
|
807
|
+
${{ needs.protected_changes.outputs.lines_changed }} lines changed)
|
|
637
808
|
|
|
638
|
-
|
|
639
|
-
|
|
809
|
+
```
|
|
810
|
+
${{ needs.protected_changes.outputs.signals }}
|
|
811
|
+
```
|
|
640
812
|
|
|
641
|
-
- **success** → step 4, then step 5 (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
Status and select the `review` verdict. A cancelled or unknown run is not evidence of
|
|
648
|
-
anything.
|
|
813
|
+
- **success** → step 4, then step 5 (review the change).
|
|
814
|
+
- **failure** → step 4, then step 6 (CI remediation).
|
|
815
|
+
- **action_required, cancelled, timed_out, or anything else** → CI did not produce a usable
|
|
816
|
+
verdict, so there is nothing to merge on. Do step 5 anyway so the report is on the record,
|
|
817
|
+
and say in `reason` which conclusion you saw. The workflow blocks on a non-success
|
|
818
|
+
conclusion without needing you to.
|
|
649
819
|
|
|
650
820
|
Follow repository documentation and established conventions when assessing or remediating
|
|
651
821
|
the pull request. Protect secrets, do not bypass checks, and keep remediation focused.
|
|
@@ -655,7 +825,7 @@ timeout-minutes: 120
|
|
|
655
825
|
`/tmp/gh-aw/agent/pr.json` for the shape of the change. If CI failed, also read
|
|
656
826
|
`/tmp/gh-aw/agent/failed-jobs.json` and `/tmp/gh-aw/agent/failed-logs.txt`.
|
|
657
827
|
|
|
658
|
-
These files are the factual basis for
|
|
828
|
+
These files are the factual basis for everything below. Do not guess — cite what you read.
|
|
659
829
|
|
|
660
830
|
4b. **Merge conflict when CI is green.** If the conclusion is `success` and
|
|
661
831
|
`has_conflicts` is `true` (current value: `${{ needs.reserve.outputs.has_conflicts }}`),
|
|
@@ -678,56 +848,92 @@ timeout-minutes: 120
|
|
|
678
848
|
branch: the current PR branch), then emit the `add_comment` with
|
|
679
849
|
**Verdict:** remediated. CI will re-run on the updated branch and the merge gate
|
|
680
850
|
will be triggered again — the next cycle will see a clean, conflict-free PR and can
|
|
681
|
-
|
|
851
|
+
reach a real disposition.
|
|
682
852
|
|
|
683
|
-
If the merge cannot be completed or the conflicts are genuinely ambiguous,
|
|
684
|
-
|
|
853
|
+
If the merge cannot be completed or the conflicts are genuinely ambiguous, do not push.
|
|
854
|
+
Report `assessed`, and say in `reason` which conflicts could not be resolved safely. An
|
|
855
|
+
unresolved conflict is not a mergeable state, so the workflow will not merge it.
|
|
685
856
|
|
|
686
857
|
If the conclusion is `success` and `has_conflicts` is `false`, skip this step and
|
|
687
858
|
proceed to step 5.
|
|
688
859
|
|
|
689
|
-
5.
|
|
860
|
+
5. Review the change. This is the part no deterministic check can do, so spend the run here.
|
|
690
861
|
|
|
691
|
-
**
|
|
692
|
-
|
|
693
|
-
|
|
862
|
+
**5a. Find defects.** Read the diff and the code it touches. You are looking for problems
|
|
863
|
+
that would matter after this merges: correctness, missing edge cases, broken contracts,
|
|
864
|
+
regressions, security, tests that do not actually test the behaviour they name.
|
|
694
865
|
|
|
695
|
-
|
|
696
|
-
credentials, or security boundaries? Flag any change to auth middleware, permission checks,
|
|
697
|
-
token issuance, or security-related config.
|
|
866
|
+
Keep a candidate only if it passes all three:
|
|
698
867
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
868
|
+
- A specific, reproducible problem in a specific file or component.
|
|
869
|
+
- Real impact: security risk, data loss, crash, or broken functionality.
|
|
870
|
+
- Something a developer could pick up and fix without further investigation.
|
|
702
871
|
|
|
703
|
-
|
|
704
|
-
|
|
872
|
+
Discard anything vague, stylistic, theoretical, or nice-to-have. **Finding nothing is a good
|
|
873
|
+
result.** An empty `findings` array on a clean change is the correct output and costs you
|
|
874
|
+
nothing. Do not pad the list.
|
|
705
875
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
876
|
+
Read `${{ env.RISK_INDICATORS }}` as a list of places worth looking first in this repository.
|
|
877
|
+
It is an attention list, not a verdict. Touching one of those areas is not a finding. A
|
|
878
|
+
defect you can demonstrate in one of them is.
|
|
709
879
|
|
|
710
|
-
**
|
|
711
|
-
|
|
712
|
-
|
|
880
|
+
**5b. Have each candidate verified independently.** Do not be the one who checks your own
|
|
881
|
+
work. Hand each candidate off for verification as a claim on its own: the file, the line,
|
|
882
|
+
what you think is wrong, and what would settle it. Do not pass on the reasoning that produced
|
|
883
|
+
it, and do not say what you hope comes back. A verifier that has read the code fresh and
|
|
884
|
+
tried to disprove the claim is the check you cannot perform on yourself.
|
|
713
885
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
886
|
+
Take the answer. Not verified means the finding is a warning at most, whatever you believed
|
|
887
|
+
when you wrote it. Verified means the verification string comes back with it, and that string
|
|
888
|
+
is the evidence the merge decision will rest on: a command with its observed output, or a
|
|
889
|
+
code path quoted end to end. Never "this looks wrong" or "this could fail if".
|
|
717
890
|
|
|
718
|
-
|
|
719
|
-
|
|
891
|
+
Two things make this cheap to do honestly. An unverified finding is capped at a warning by the
|
|
892
|
+
workflow whatever severity you claim, so overstating one gains you nothing. A verified high or
|
|
893
|
+
critical finding blocks the merge, so inventing one costs somebody a morning.
|
|
720
894
|
|
|
721
|
-
|
|
722
|
-
|
|
895
|
+
With no candidates, verify nothing and move on. This step exists for claims, not for
|
|
896
|
+
reassurance about their absence.
|
|
723
897
|
|
|
724
|
-
**Check
|
|
725
|
-
|
|
726
|
-
|
|
898
|
+
**5c. Check the acceptance criteria.** The issue context at `${{ env.ISSUE_CONTEXT_PATH }}`
|
|
899
|
+
says what this change was supposed to do. Confirm the diff does it. Set
|
|
900
|
+
`acceptanceCriteriaMet` to false only when you can name a criterion the diff does not
|
|
901
|
+
satisfy.
|
|
727
902
|
|
|
728
|
-
**
|
|
729
|
-
|
|
730
|
-
|
|
903
|
+
**5d. Answer the recoverability checklist.** How easy would this be to undo if it were
|
|
904
|
+
wrong? Cite the diff for each answer, and record the ones that fired in
|
|
905
|
+
`recoverabilitySignals`:
|
|
906
|
+
|
|
907
|
+
- behind a feature flag
|
|
908
|
+
- revertible by reverting the commit, with no manual step
|
|
909
|
+
- no persistent data mutated
|
|
910
|
+
- no irreversible migration
|
|
911
|
+
- backward compatible with existing callers and stored data
|
|
912
|
+
- observable after deploy
|
|
913
|
+
- small affected surface
|
|
914
|
+
|
|
915
|
+
`high` when the change can be reverted cleanly and touches no persistent state. `medium` when
|
|
916
|
+
a revert works but something (a cache, a config, a client) needs attention. `low` when a
|
|
917
|
+
revert would not restore the previous behaviour: a migration that drops or rewrites data, a
|
|
918
|
+
contract other repositories already consume, anything that leaves state behind.
|
|
919
|
+
|
|
920
|
+
**A `low` rating must name what cannot be undone**, in `recoverabilitySignals`. `low` parks
|
|
921
|
+
the pull request for a person, so it is the one judgement of yours that can hold up a merge on
|
|
922
|
+
its own, and the same rule applies to it as to a finding: unevidenced, it does not count. A
|
|
923
|
+
`low` with an empty `recoverabilitySignals` is read as `medium`. This is not an invitation to
|
|
924
|
+
pad the list — it is the difference between "this rewrites the plan rows in place" and a
|
|
925
|
+
reflex.
|
|
926
|
+
|
|
927
|
+
**5e. Raise the blast radius if the paths missed something.** The measured level came from
|
|
928
|
+
file paths and diff shape. If the change introduces something those rules cannot see — a new
|
|
929
|
+
authorization decision point, a new trust boundary, a write to shared state from a path that
|
|
930
|
+
never wrote before — set `blastRadiusRaise` with the level and the reason. You can only raise
|
|
931
|
+
it. A lower value is ignored.
|
|
932
|
+
|
|
933
|
+
**5f. State your confidence.** A number between 0 and 1, for the whole assessment, not for
|
|
934
|
+
any one finding. Below ${{ env.CONFIDENCE_THRESHOLD }} sends the pull request to a person, so
|
|
935
|
+
it is the honest way to say you could not get comfortable. Use it when the change is in an
|
|
936
|
+
area you could not fully trace, not as a reflex.
|
|
731
937
|
|
|
732
938
|
6. **CI failed** → read `/tmp/gh-aw/agent/failed-jobs.json` and
|
|
733
939
|
`/tmp/gh-aw/agent/failed-logs.txt`, which are already on disk. Load only skills required to
|
|
@@ -776,52 +982,65 @@ timeout-minutes: 120
|
|
|
776
982
|
the current PR branch), then select the `remediated` verdict. CI will run again and trigger
|
|
777
983
|
you again with the new result.
|
|
778
984
|
|
|
779
|
-
If you cannot fix it after a concrete repair attempt, or the logs show you have already tried
|
|
780
|
-
stop looping:
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
7. Decide the verdict based on the assessment table:
|
|
784
|
-
|
|
785
|
-
- **All checks ✅ → `merge`.** The PR is safe to merge. CI is green, no risk indicators
|
|
786
|
-
triggered, tests are intact, scope matches, mergeability is clean.
|
|
787
|
-
- **Any check ⚠️ or ❌ (except CI failure) → `review`.** Do not merge. Explain exactly which
|
|
788
|
-
check tripped, why, and what a reviewer should look at. Leave `implement` in place: the
|
|
789
|
-
work is not finished until a human merges it.
|
|
790
|
-
- **CI failed and you fixed it → `remediated`.** You pushed a verified fix and CI will
|
|
791
|
-
re-run.
|
|
792
|
-
- **CI failed and you cannot fix it → `review`.** Explain the failure and what you tried.
|
|
985
|
+
If you cannot fix it after a concrete repair attempt, or the logs show you have already tried
|
|
986
|
+
on this same head commit, stop looping: report `assessed` with no push, and say in `reason`
|
|
987
|
+
what failed and what you tried. CI is not green, so the workflow blocks the pull request and
|
|
988
|
+
a person decides from there.
|
|
793
989
|
|
|
794
|
-
|
|
795
|
-
is refused, that refusal is the answer: select `review` and leave it for a human.
|
|
990
|
+
7. Say which of two things you did, and nothing more.
|
|
796
991
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
3. A structured assessment table with all 10 check results
|
|
801
|
-
4. A one-line detail per check (what was found and why it passed or flagged)
|
|
802
|
-
5. A line `**Verdict:** merge`, `**Verdict:** review`, or `**Verdict:** remediated`
|
|
992
|
+
- **`remediated`** — CI failed or the branch conflicted, you fixed it, you verified the fix,
|
|
993
|
+
and you are pushing it. Exactly one `push_to_pull_request_branch` goes with this word.
|
|
994
|
+
- **`assessed`** — you reviewed the change and are reporting what you found. No push.
|
|
803
995
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
and
|
|
996
|
+
These are the only two words the workflow accepts. You do not write `merge`, `review`,
|
|
997
|
+
`auto-merge`, `blocked`, or any other outcome: the workflow computes the disposition from
|
|
998
|
+
your report and from the facts it measured, and a word it does not recognise parks the pull
|
|
999
|
+
request. Never merge with administrator privileges and never bypass a required check.
|
|
807
1000
|
|
|
808
|
-
|
|
1001
|
+
8. Emit exactly one `add_comment` targeting issue `${{ needs.subject.outputs.issue }}`,
|
|
1002
|
+
containing, in this order:
|
|
809
1003
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1004
|
+
1. `${{ env.GATE_MARKER }}`
|
|
1005
|
+
2. A heading: `## Merge gate review of PR #${{ needs.subject.outputs.pr }}`
|
|
1006
|
+
3. A line `**Verdict:** assessed` or `**Verdict:** remediated`
|
|
1007
|
+
4. Prose a person can read: what this change does, what you looked at, what you found or did
|
|
1008
|
+
not find, and what you ran to check. If you remediated, say what failed and what you
|
|
1009
|
+
changed. Short. Nobody reads a wall.
|
|
1010
|
+
5. A fenced `json` block, exactly one, as the last thing in the comment.
|
|
1011
|
+
|
|
1012
|
+
The JSON block is what the workflow reads. Every field is required except
|
|
1013
|
+
`blastRadiusRaise`, which is omitted when the measured level stands:
|
|
1014
|
+
|
|
1015
|
+
```json
|
|
1016
|
+
{
|
|
1017
|
+
"findings": [
|
|
1018
|
+
{
|
|
1019
|
+
"severity": "critical|high|medium|low",
|
|
1020
|
+
"confidence": 0.0,
|
|
1021
|
+
"verified": true,
|
|
1022
|
+
"verification": "the command you ran and what it printed, or the code path quoted end to end",
|
|
1023
|
+
"category": "correctness|security|contract|tests|regression",
|
|
1024
|
+
"file": "src/...",
|
|
1025
|
+
"line": 0,
|
|
1026
|
+
"finding": "one sentence: what is wrong",
|
|
1027
|
+
"evidence": "what in the diff or the code shows it",
|
|
1028
|
+
"suggestedFix": "what would fix it"
|
|
1029
|
+
}
|
|
1030
|
+
],
|
|
1031
|
+
"recoverability": "high|medium|low",
|
|
1032
|
+
"recoverabilitySignals": ["revertible with no manual step", "no persistent state written"],
|
|
1033
|
+
"blastRadiusRaise": { "to": "high", "reason": "adds a new authorization decision point" },
|
|
1034
|
+
"acceptanceCriteriaMet": true,
|
|
1035
|
+
"confidence": 0.0,
|
|
1036
|
+
"reason": "one sentence a person would accept as the summary"
|
|
1037
|
+
}
|
|
824
1038
|
```
|
|
825
1039
|
|
|
826
|
-
|
|
1040
|
+
`"findings": []` on a clean change is the expected output, not a failure to do the job.
|
|
827
1041
|
|
|
1042
|
+
The workflow applies comments, labels, merges, and closures with the App token. Reading the
|
|
1043
|
+
repository, running verification commands and delegating a finding to be checked are all part
|
|
1044
|
+
of the job. What is restricted is what leaves this run: the only safe outputs you may call are
|
|
1045
|
+
the one optional `push_to_pull_request_branch` for a verified repair and this one
|
|
1046
|
+
`add_comment`.
|