@plainconceptsplatform/workflows 1.4.0 → 1.6.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.
@@ -2,7 +2,7 @@
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: "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."
5
+ REPO_RULES: "Review the selected 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
6
  # Where to look first, not what to escalate on. This list used to be the check that decided
7
7
  # whether a machine merged without a human, and it decided by category: the prompt told the
8
8
  # agent that a match "is not a defect, it is a reason this pull request needs a person". In a
@@ -13,34 +13,35 @@ env:
13
13
  # One line: gh-aw joins a multi-line env value onto a single line when it compiles the lock.
14
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."
15
15
  # Paths a bot may change but never merge on its own: an extended regular expression matched
16
- # against every changed path in the pull request. The default names this stack's dependency
17
- # and toolchain manifests plus everything under a dotted directory, and is wrong for a
18
- # repository built on anything else, which is the failure worth knowing about: an unmatched
19
- # list protects nothing and reports nothing. A match holds the merge for a human; it does not
20
- # stop the agent repairing failed CI on the same files.
21
- PROTECTED_PATHS: '^(\.|AGENTS\.md$|ARCHITECTURE\.md$|opencode\.jsonc$|package\.json$|pnpm-lock\.yaml$|Directory\.Packages\.props$|global\.json$)'
16
+ # against every changed path in the pull request. A match holds the merge for a human; it does
17
+ # not stop the agent repairing failed CI on the same files.
18
+ #
19
+ # Only the automation itself. This list used to hold every dependency manifest, lockfile and
20
+ # dotted directory as well, and with a size threshold and an owner list as wide it parked most
21
+ # of a night's pull requests for a person who then merged them by hand, unread, on Monday:
22
+ # in Pliny-Bot 32 of 40 bot pull requests in two days. What stops a merge now is what is
23
+ # genuinely dangerous to merge unseen: the workflows, the secrets, and a change too big to
24
+ # review in one pass.
25
+ PROTECTED_PATHS: '^\.github/'
22
26
  # Paths whose change needs the person who owns them. A match forces OWNER REVIEW REQUIRED and
23
27
  # 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.
28
+ # when the repository has that file; it is never a prerequisite.
26
29
  #
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]+$)'
30
+ # Secrets: a `secrets` directory or file, an environment file, a key or a certificate. Code
31
+ # that handles secrets (a SecretMasker.cs) is not a secret; the review pass reads it like any
32
+ # other change.
33
+ OWNER_PATHS: '(^|/)([Ss]ecrets?(/|\.[A-Za-z0-9]+$)|\.env(\.[A-Za-z0-9_-]+)?$|[^/]+\.(pem|pfx|p12|key)$)'
34
34
  # Paths worth a second look that do not, alone, need a person. A match raises the floor to
35
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"
36
+ # separates "look here" from "stop here". Auth, identity, security, migrations and
37
+ # infrastructure used to stop a merge from here; they are the review pass's to look at now, and
38
+ # a migration that rewrites data still parks through a `low` recoverability the agent evidences.
39
+ SENSITIVE_PATHS: '(^|/)([Dd]omain|entities|[Cc]ontracts)/|(^|/)([Aa]uth|[Aa]uthn|[Aa]uthz|[Aa]uthentication|[Aa]uthorization|[Ii]dentity|[Ss]ecurity|[Mm]igrations|[Ii]nfra|terraform|helm|k8s|deploy)(/|[A-Z][A-Za-z]*\.[a-z]+$)'
40
+ # Diff shape. Lines decide high: a change over a thousand counted lines (tests, docs,
41
+ # translations and generated code excluded) is too big to merge unseen. A file count alone
42
+ # never does, so the high file threshold is set out of reach; spread still raises medium.
43
+ BLAST_HIGH_FILES: "100000"
44
+ BLAST_HIGH_LINES: "1000"
44
45
  BLAST_MEDIUM_FILES: "5"
45
46
  BLAST_MEDIUM_LINES: "200"
46
47
  # Paths that change no runtime behaviour: tests, docs, specs, translations and generated
@@ -80,14 +81,18 @@ env:
80
81
  PARK_AT_UNUSABLE_OUTPUT: "2"
81
82
  ISSUE_CONTEXT_PATH: /tmp/gh-aw/agent/issue-context.json
82
83
  GH_AW_ALLOWED_BOTS: "platform-devbox[bot],github-actions[bot]"
84
+ # The bots whose pull requests the gate takes, in every spelling GitHub uses for one account.
85
+ # Any other author needs write access to the repository, as they would to push the branch.
86
+ TRUSTED_BOTS: "github-actions[bot] app/github-actions platform-devbox[bot]"
83
87
  GIT_AUTHOR_NAME: "github-actions[bot]"
84
88
  GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
85
89
  GIT_COMMITTER_NAME: "github-actions[bot]"
86
90
  GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com"
87
91
  description: |
88
- Decides what happens to a bot-authored pull request once CI has reported: merge when the
89
- risk assessment is clean, hand to a human when it is not, fix CI when it failed. Called by
90
- the Work Router; does not trigger on public events.
92
+ Decides what happens to a pull request once CI has reported, whether the bot or a person with
93
+ write access opened it: merge when the risk assessment is clean, hand to a human when it is
94
+ genuinely dangerous, fix CI or a conflict when there is one. Called by the Work Router, one
95
+ pull request at a time by its merge train; does not trigger on public events.
91
96
 
92
97
  In a stack, a child pull request (its base is the feature branch) merges into that feature
93
98
  branch and its child issue closes with it; the root pull request is held for a person. The
@@ -154,6 +159,10 @@ jobs:
154
159
  found: ${{ steps.subject.outputs.found }}
155
160
  pr: ${{ steps.subject.outputs.pr }}
156
161
  issue: ${{ steps.subject.outputs.issue }}
162
+ # Where verdicts, attempts and labels go: the issue, or the pull request when it closes none.
163
+ record: ${{ steps.subject.outputs.record }}
164
+ head_ref: ${{ steps.subject.outputs.head-ref }}
165
+ closes: ${{ steps.subject.outputs.closes }}
157
166
  conclusion: ${{ steps.subject.outputs.conclusion }}
158
167
  run-id: ${{ steps.subject.outputs.run-id }}
159
168
  review_blocked: ${{ steps.review.outputs.review_blocked }}
@@ -174,6 +183,7 @@ jobs:
174
183
  ci-run-id: ${{ inputs.ci-run-id }}
175
184
  linked-issue: ${{ inputs.linked-issue }}
176
185
  require-label: ${{ env.IMPLEMENT_LABEL }}
186
+ trusted-bots: ${{ env.TRUSTED_BOTS }}
177
187
  - name: Block a pull request with requested changes
178
188
  id: review
179
189
  env:
@@ -354,14 +364,14 @@ jobs:
354
364
  uses: ./.github/actions/remove-issue-labels
355
365
  with:
356
366
  token: ${{ steps.app-token.outputs.token }}
357
- issue-number: ${{ needs.subject.outputs.issue }}
367
+ issue-number: ${{ needs.subject.outputs.record }}
358
368
  labels: ${{ env.WORKING_LABEL }}
359
369
  - name: Flag owner review
360
370
  if: needs.protected_changes.outputs.holds_review == 'true'
361
371
  uses: ./.github/actions/add-issue-labels
362
372
  with:
363
373
  token: ${{ steps.app-token.outputs.token }}
364
- issue-number: ${{ needs.subject.outputs.issue }}
374
+ issue-number: ${{ needs.subject.outputs.record }}
365
375
  labels: |-
366
376
  ${{ env.REVIEW_LABEL }}
367
377
  ${{ env.OWNER_REVIEW_LABEL }}
@@ -370,7 +380,7 @@ jobs:
370
380
  uses: ./.github/actions/create-issue-comment
371
381
  with:
372
382
  token: ${{ steps.app-token.outputs.token }}
373
- issue-number: ${{ needs.subject.outputs.issue }}
383
+ issue-number: ${{ needs.subject.outputs.record }}
374
384
  body: |
375
385
  ${{ env.GATE_MARKER }}
376
386
  PR #${{ needs.subject.outputs.pr }} changes protected files and cannot be auto-merged.
@@ -439,7 +449,7 @@ jobs:
439
449
  uses: ./.github/actions/create-issue-comment
440
450
  with:
441
451
  token: ${{ steps.app-token.outputs.token }}
442
- issue-number: ${{ needs.subject.outputs.issue }}
452
+ issue-number: ${{ needs.subject.outputs.record }}
443
453
  body: |
444
454
  Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || 'CI failed.' }}
445
455
  Bot is working on fixing it.
@@ -455,6 +465,7 @@ jobs:
455
465
  outputs:
456
466
  valid: ${{ steps.validate.outputs.valid }}
457
467
  outcome: ${{ steps.validate.outputs.outcome }}
468
+ pushed: ${{ steps.validate.outputs.pushed }}
458
469
  steps:
459
470
  - name: Checkout workflow actions
460
471
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -472,7 +483,7 @@ jobs:
472
483
  uses: ./.github/actions/validate-merge-gate-output
473
484
  with:
474
485
  output-file: ${{ steps.output.outputs.output-file }}
475
- issue-number: ${{ needs.subject.outputs.issue }}
486
+ issue-number: ${{ needs.subject.outputs.record }}
476
487
  ci-conclusion: ${{ needs.subject.outputs.conclusion }}
477
488
  blast-level: ${{ needs.protected_changes.outputs.level }}
478
489
  protected-hit: ${{ needs.protected_changes.outputs.requires_review }}
@@ -510,18 +521,23 @@ jobs:
510
521
  token: ${{ steps.app-token.outputs.token }}
511
522
  fetch-depth: 0
512
523
  - name: Verify pull request closes the source issue
524
+ if: needs.subject.outputs.issue != ''
513
525
  continue-on-error: true
514
526
  uses: ./.github/actions/link-pr-to-issue
515
527
  with:
516
528
  token: ${{ steps.app-token.outputs.token }}
517
529
  pr-number: ${{ needs.subject.outputs.pr }}
518
530
  issue-number: ${{ needs.subject.outputs.issue }}
531
+ # The push goes to this pull request's branch or nowhere. A `[bot] ` title prefix used to be
532
+ # the guard, which a person's pull request does not carry; the branch the subject job read
533
+ # is the exact one, whoever opened it.
519
534
  - name: Apply agent output
520
535
  uses: ./.github/actions/apply-agent-output
521
536
  with:
522
537
  artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
523
538
  token: ${{ steps.app-token.outputs.token }}
524
539
  push-to-branch: 'true'
540
+ expected-branch: ${{ needs.subject.outputs.head_ref }}
525
541
  apply-labels: 'false'
526
542
  # GITHUB_TOKEN on purpose: an App-token comment on a pull request is an issue_comment
527
543
  # event, and GITHUB_TOKEN raises none. The full assessment lives on the issue, where the
@@ -557,7 +573,7 @@ jobs:
557
573
  ${{ needs.protected_changes.outputs.signals }}
558
574
  ```
559
575
 
560
- 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 }})
576
+ ${{ needs.subject.outputs.issue != '' && format('Findings and verification on the linked issue: #{0}.', needs.subject.outputs.issue) || 'Findings and verification are in the review comment above.' }} [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
561
577
  ${{ env.EVIDENCE_BEFORE_REVIEW == 'true' && contains(fromJson('["human-review","owner-review"]'), needs.validate_output.outputs.outcome) && 'Screenshots of this head follow in their own comment if it carries a verification plan. Approving the pull request merges it.' || '' }}
562
578
  - name: Merge approved pull request
563
579
  if: needs.validate_output.outputs.outcome == 'auto-merge' && needs.subject.outputs.stack_root != 'true'
@@ -578,7 +594,7 @@ jobs:
578
594
  uses: ./.github/actions/create-issue-comment
579
595
  with:
580
596
  token: ${{ steps.app-token.outputs.token }}
581
- issue-number: ${{ needs.subject.outputs.issue }}
597
+ issue-number: ${{ needs.subject.outputs.record }}
582
598
  body: |
583
599
  **Stack root held.** This pull request is the root of a stack: its children merge
584
600
  into the feature branch, and it merges into ${{ github.event.repository.default_branch }} only when a person
@@ -588,7 +604,7 @@ jobs:
588
604
  uses: ./.github/actions/remove-issue-labels
589
605
  with:
590
606
  token: ${{ steps.app-token.outputs.token }}
591
- issue-number: ${{ needs.subject.outputs.issue }}
607
+ issue-number: ${{ needs.subject.outputs.record }}
592
608
  labels: ${{ env.WORKING_LABEL }}
593
609
  # `review` goes on for all three parked dispositions, so every board query and every
594
610
  # authorize-bot-work handoff that already reads it keeps working. The second label is what
@@ -598,7 +614,7 @@ jobs:
598
614
  uses: ./.github/actions/add-issue-labels
599
615
  with:
600
616
  token: ${{ steps.app-token.outputs.token }}
601
- issue-number: ${{ needs.subject.outputs.issue }}
617
+ issue-number: ${{ needs.subject.outputs.record }}
602
618
  labels: |-
603
619
  ${{ env.REVIEW_LABEL }}
604
620
  ${{ needs.validate_output.outputs.outcome == 'owner-review' && env.OWNER_REVIEW_LABEL || '' }}
@@ -630,7 +646,7 @@ jobs:
630
646
  uses: ./.github/actions/remove-issue-labels
631
647
  with:
632
648
  token: ${{ steps.app-token.outputs.token }}
633
- issue-number: ${{ needs.subject.outputs.issue }}
649
+ issue-number: ${{ needs.subject.outputs.record }}
634
650
  labels: ${{ env.WORKING_LABEL }}
635
651
  # GitHub's closing keywords never fire when a PR merges into a branch other than the
636
652
  # default, so a child PR landing on the feature branch leaves its issue open — and
@@ -640,7 +656,7 @@ jobs:
640
656
  # and `gh issue close` on an already-closed issue is a harmless no-op, making the
641
657
  # step safe to re-run.
642
658
  - name: Close the child issue when the pull request landed on a feature branch
643
- if: needs.validate_output.outputs.outcome == 'auto-merge' && needs.subject.outputs.pr_base != '' && needs.subject.outputs.pr_base != github.event.repository.default_branch
659
+ if: needs.validate_output.outputs.outcome == 'auto-merge' && needs.subject.outputs.issue != '' && needs.subject.outputs.pr_base != '' && needs.subject.outputs.pr_base != github.event.repository.default_branch
644
660
  env:
645
661
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
646
662
  REPO: ${{ github.repository }}
@@ -667,7 +683,7 @@ jobs:
667
683
  uses: ./.github/actions/remove-issue-labels
668
684
  with:
669
685
  token: ${{ steps.app-token.outputs.token }}
670
- issue-number: ${{ needs.subject.outputs.issue }}
686
+ issue-number: ${{ needs.subject.outputs.record }}
671
687
  labels: |
672
688
  ${{ env.IMPLEMENT_LABEL }}
673
689
  ${{ env.WORKING_LABEL }}
@@ -744,18 +760,18 @@ jobs:
744
760
  uses: ./.github/actions/create-issue-comment
745
761
  with:
746
762
  token: ${{ steps.app-token.outputs.token }}
747
- issue-number: ${{ needs.subject.outputs.issue }}
763
+ issue-number: ${{ needs.subject.outputs.record }}
748
764
  body: |
749
765
  ${{ env.ATTEMPT_MARKER }}
750
766
  Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ env.MAX_ATTEMPTS }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
751
- This failure parks at ${{ steps.budget.outputs.threshold }}. The issue keeps `implement`; the merge belt will retry.
767
+ This failure parks at ${{ steps.budget.outputs.threshold }}. The merge train will retry.
752
768
  [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
753
769
  - name: Report the exhausted attempt budget
754
770
  if: steps.budget.outputs.park == 'true' && steps.budget.outputs.kind == 'machine'
755
771
  uses: ./.github/actions/create-issue-comment
756
772
  with:
757
773
  token: ${{ steps.app-token.outputs.token }}
758
- issue-number: ${{ needs.subject.outputs.issue }}
774
+ issue-number: ${{ needs.subject.outputs.record }}
759
775
  body: |
760
776
  ${{ env.ATTEMPT_MARKER }}
761
777
  Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ steps.budget.outputs.threshold }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
@@ -773,7 +789,7 @@ jobs:
773
789
  uses: ./.github/actions/create-issue-comment
774
790
  with:
775
791
  token: ${{ steps.app-token.outputs.token }}
776
- issue-number: ${{ needs.subject.outputs.issue }}
792
+ issue-number: ${{ needs.subject.outputs.record }}
777
793
  body: |
778
794
  ${{ env.GATE_MARKER }}
779
795
  The agent finished on PR #${{ needs.subject.outputs.pr }} but its report could not be
@@ -783,6 +799,26 @@ jobs:
783
799
 
784
800
  **Verdict:** human-review
785
801
  [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
802
+ # The issue is where the belt reads verdicts, but the pull request is where people look. A
803
+ # parked pull request with nothing on it reads as abandoned, and a repair the agent pushed
804
+ # that the gate refused to apply sits in the run artifact where nobody finds it.
805
+ - name: Tell the pull request it needs a person
806
+ if: steps.budget.outputs.park == 'true'
807
+ uses: ./.github/actions/create-issue-comment
808
+ with:
809
+ token: ${{ steps.app-token.outputs.token }}
810
+ issue-number: ${{ needs.subject.outputs.pr }}
811
+ body: |
812
+ The merge gate stopped on this pull request and handed it to a person${{ needs.subject.outputs.issue != '' && format(' (#{0})', needs.subject.outputs.issue) || '' }}.
813
+ ${{ needs.validate_output.outputs.pushed == 'true' && 'The agent pushed a repair that was not applied because its report could not be read. The patch and bundle are in the `agent` artifact of the run below.' || '' }}
814
+ [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
815
+ - name: Label the pull request for review
816
+ if: steps.budget.outputs.park == 'true'
817
+ uses: ./.github/actions/add-issue-labels
818
+ with:
819
+ token: ${{ steps.app-token.outputs.token }}
820
+ issue-number: ${{ needs.subject.outputs.pr }}
821
+ labels: ${{ env.REVIEW_LABEL }}
786
822
  # `stalled` means a park the machine caused, and the janitor retries those. An unusable
787
823
  # report is a park the machine caused and retrying reproduces it, so it gets `review`
788
824
  # alone: the janitor's own rule is retry a failure, report a decision.
@@ -791,7 +827,7 @@ jobs:
791
827
  uses: ./.github/actions/add-issue-labels
792
828
  with:
793
829
  token: ${{ steps.app-token.outputs.token }}
794
- issue-number: ${{ needs.subject.outputs.issue }}
830
+ issue-number: ${{ needs.subject.outputs.record }}
795
831
  labels: |-
796
832
  ${{ env.REVIEW_LABEL }}
797
833
  ${{ steps.budget.outputs.kind == 'machine' && env.STALLED_LABEL || '' }}
@@ -801,7 +837,7 @@ jobs:
801
837
  uses: ./.github/actions/remove-issue-labels
802
838
  with:
803
839
  token: ${{ steps.app-token.outputs.token }}
804
- issue-number: ${{ needs.subject.outputs.issue }}
840
+ issue-number: ${{ needs.subject.outputs.record }}
805
841
  labels: ${{ env.WORKING_LABEL }}
806
842
 
807
843
  agent:
@@ -871,7 +907,7 @@ steps:
871
907
  uses: ./.github/actions/load-issue-context
872
908
  with:
873
909
  token: ${{ github.token }}
874
- issue-number: ${{ needs.subject.outputs.issue }}
910
+ issue-number: ${{ needs.subject.outputs.record }}
875
911
  output-path: ${{ env.ISSUE_CONTEXT_PATH }}
876
912
  - name: Fetch the diff and any failing job logs
877
913
  env:
@@ -913,9 +949,10 @@ safe-outputs:
913
949
  # target "*" because these workers are dispatched, not triggered by the pull request:
914
950
  # the default "triggering" target has no pull request in context and rejects the push
915
951
  # with "requires pull request context", so the agent's fix is computed and discarded.
952
+ # No title prefix: the gate repairs pull requests people opened as well, and conclude pushes
953
+ # only to the branch of the pull request the subject job validated.
916
954
  push-to-pull-request-branch:
917
955
  target: "*"
918
- required-title-prefix: "[bot] "
919
956
  max-patch-size: 10240
920
957
  # A failed bot PR may already contain protected files. Permit a verified repair push,
921
958
  # but protected_changes still prevents the later green-CI cycle from auto-merging it.
@@ -934,13 +971,13 @@ safe-outputs:
934
971
  timeout-minutes: 120
935
972
  ---
936
973
 
937
- 1. You are gating pull request **#${{ needs.subject.outputs.pr }}**, which closes issue
938
- **#${{ needs.subject.outputs.issue }}**. CI concluded
939
- **${{ needs.subject.outputs.conclusion }}**.
974
+ 1. You are gating pull request **#${{ needs.subject.outputs.pr }}**, which ${{ needs.subject.outputs.closes }}.
975
+ CI concluded **${{ needs.subject.outputs.conclusion }}**.
940
976
 
941
- It has already been confirmed that this is an open pull request we authored, that it closes
942
- an issue, and that the issue carries `implement`. Do not re-check any of that, and do not
943
- poll for checks: the conclusion above is the answer.
977
+ It has already been confirmed that this is an open pull request from this repository, opened
978
+ by the bot or by someone with write access. Do not re-check any of that, and do not poll for
979
+ checks: the conclusion above is the answer. Who opened it changes nothing below: a person's
980
+ pull request is repaired, reviewed and merged by the same rules as the bot's.
944
981
 
945
982
  You are on the pull request branch. Never rebase, reset, amend or otherwise rewrite
946
983
  history: the workflow applies your commits as a bundle with a fast-forward-only push and
@@ -948,9 +985,11 @@ timeout-minutes: 120
948
985
  `push_to_pull_request_branch` tool's own description recommends rebasing; in this
949
986
  repository that advice is wrong. Merge, commit, and let the workflow push.
950
987
 
951
- 2. Read `${{ env.ISSUE_CONTEXT_PATH }}`. It contains the issue body and its discussion. The
988
+ 2. Read `${{ env.ISSUE_CONTEXT_PATH }}`. It contains the issue body and its discussion, or, for
989
+ a pull request that closes no issue, the pull request's own description and discussion. The
952
990
  acceptance criteria there define what this implementation had to satisfy, and step 5c asks
953
- you to check the diff against them.
991
+ you to check the diff against them. A description with no stated criteria is checked against
992
+ what it says the change does.
954
993
 
955
994
  3. Branch on the conclusion.
956
995
 
@@ -979,6 +1018,13 @@ timeout-minutes: 120
979
1018
  until it exits 0, then verify, push and report `remediated` as in step 6. An `assessed`
980
1019
  verdict while it fails blocks the pull request.
981
1020
 
1021
+ When CI failed as well, repair CI first and `check-diff` second, and verify again after the
1022
+ `check-diff` repair. The two pull in opposite directions: a complexity or coverage gate wants
1023
+ more code and tests, `check-diff` wants fewer lines. Trimming to fit `check-diff` is how a
1024
+ pull request that once passed broke the formatter, Semgrep and the complexity gate on its
1025
+ last commit (Pliny-Bot #633). Never delete a test that covers a branch the change added; if
1026
+ both cannot hold, keep CI green and say so in `reason`.
1027
+
982
1028
  - **success** → step 4, then step 5 (review the change).
983
1029
  - **failure** → step 4, then step 6 (CI remediation).
984
1030
  - **action_required, cancelled, timed_out, or anything else** → CI did not produce a usable
@@ -1197,7 +1243,7 @@ timeout-minutes: 120
1197
1243
  your report and from the facts it measured, and a word it does not recognise parks the pull
1198
1244
  request. Never merge with administrator privileges and never bypass a required check.
1199
1245
 
1200
- 8. Emit exactly one `add_comment` targeting issue `${{ needs.subject.outputs.issue }}`,
1246
+ 8. Emit exactly one `add_comment` targeting item `${{ needs.subject.outputs.record }}`,
1201
1247
  containing, in this order:
1202
1248
 
1203
1249
  1. `${{ env.GATE_MARKER }}`