@plainconceptsplatform/workflows 0.4.11 → 0.4.13

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.
@@ -11,7 +11,7 @@ set -euo pipefail
11
11
  readonly AUDIT_CRON="17 1 * * 1"
12
12
  readonly AUDIT_CLOSE_CRON="43 3 * * *"
13
13
  readonly CLEANUP_ARTIFACTS_CRON="0 6 * * *"
14
- readonly RECONCILE_BOT_PR_RUNS_CRON="*/5 * * * *"
14
+ readonly RECONCILE_BOT_PR_RUNS_CRON="*/15 * * * *"
15
15
  readonly STALE_RECOVERY_CRON="0 */2 * * *"
16
16
  # Daily, but it proposes far less often than daily: the worker holds one open
17
17
  # proposal at a time and skips while that slot is filled. The cron is a heartbeat,
@@ -35,19 +35,27 @@ classify_route() {
35
35
  issues)
36
36
  if [ "${ACTION:-}" = "labeled" ]; then
37
37
  case "${LABEL:-}" in
38
- refine)
39
- route="refine"
40
- refine_mode="first"
41
- issue_number="${EVENT_ISSUE_NUMBER:-}"
42
- ;;
43
- implement)
44
- route="implement"
45
- issue_number="${EVENT_ISSUE_NUMBER:-}"
38
+ bot-working)
39
+ # Bot adds bot-working → route based on which work label is present
40
+ if has_label implement; then
41
+ route="implement"
42
+ issue_number="${EVENT_ISSUE_NUMBER:-}"
43
+ elif has_label refine; then
44
+ route="refine"
45
+ refine_mode="first"
46
+ issue_number="${EVENT_ISSUE_NUMBER:-}"
47
+ elif has_label direct; then
48
+ route="direct"
49
+ direct_mode="first"
50
+ issue_number="${EVENT_ISSUE_NUMBER:-}"
51
+ else
52
+ error="bot-working added but no work label (implement/refine/direct) found"
53
+ fi
46
54
  ;;
47
- direct)
48
- route="direct"
49
- direct_mode="first"
50
- issue_number="${EVENT_ISSUE_NUMBER:-}"
55
+ refine | implement | direct)
56
+ # Human adds work label → authorize-bot-work.yml handles this
57
+ # Route to none here; the bot will add bot-working which triggers the actual work
58
+ error="waiting for bot to add bot-working label"
51
59
  ;;
52
60
  esac
53
61
  fi
@@ -78,7 +86,17 @@ classify_route() {
78
86
  ;;
79
87
 
80
88
  pull_request_target)
81
- route="bot-approve"
89
+ if [ "${ACTION:-}" = "labeled" ] && [ "${LABEL:-}" = "merge-gate" ]; then
90
+ # Human adds merge-gate label to bot PR → triggers merge-gate with human actor
91
+ # This bypasses gh-aw's bot membership check since the actor is human
92
+ route="merge-gate"
93
+ pr_number="${EVENT_PR_NUMBER:-}"
94
+ # CI status will be fetched by the merge-gate workflow
95
+ ci_conclusion=""
96
+ ci_run_id=""
97
+ else
98
+ route="bot-approve"
99
+ fi
82
100
  ;;
83
101
 
84
102
  workflow_run)
@@ -9,8 +9,9 @@ inputs:
9
9
  description: Pull request number to gate.
10
10
  required: true
11
11
  ci-conclusion:
12
- description: Conclusion reported by the CI run that triggered this gate.
13
- required: true
12
+ description: Conclusion reported by the CI run that triggered this gate. If empty, fetches from the PR's latest CI run.
13
+ required: false
14
+ default: ''
14
15
  linked-issue:
15
16
  description: Issue the pull request closes, when the router already resolved it.
16
17
  required: false
@@ -30,8 +31,11 @@ outputs:
30
31
  description: Issue number the pull request closes.
31
32
  value: ${{ steps.identify.outputs.issue }}
32
33
  conclusion:
33
- description: CI conclusion, passed through unchanged.
34
+ description: CI conclusion, passed through unchanged or fetched from latest CI run.
34
35
  value: ${{ steps.identify.outputs.conclusion }}
36
+ run-id:
37
+ description: CI run ID when fetched.
38
+ value: ${{ steps.identify.outputs.run-id }}
35
39
  runs:
36
40
  using: composite
37
41
  steps:
@@ -87,11 +91,30 @@ runs:
87
91
  fi
88
92
  fi
89
93
 
94
+ # Fetch CI conclusion if not provided (label-triggered runs)
95
+ ci_conclusion="$CONCLUSION"
96
+ ci_run_id=""
97
+ if [ -z "$ci_conclusion" ]; then
98
+ head_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
99
+ ci_run="$(gh api "repos/$REPO/actions/runs?head_sha=$head_sha&per_page=100" \
100
+ --jq '[.workflow_runs[] | select(.name == "App: CI" or .name == "CI")] | sort_by(.created_at) | last')"
101
+
102
+ if [ -n "$ci_run" ] && [ "$ci_run" != "null" ]; then
103
+ ci_conclusion="$(jq -r '.conclusion // "pending"' <<<"$ci_run")"
104
+ ci_run_id="$(jq -r '.id' <<<"$ci_run")"
105
+ echo "::notice::Fetched CI status: $ci_conclusion (run $ci_run_id)"
106
+ else
107
+ ci_conclusion="unknown"
108
+ echo "::warning::No CI run found for PR #$PR_NUMBER"
109
+ fi
110
+ fi
111
+
90
112
  {
91
113
  echo "found=true"
92
114
  echo "pr=$PR_NUMBER"
93
115
  echo "issue=$issue"
94
- echo "conclusion=$CONCLUSION"
116
+ echo "conclusion=$ci_conclusion"
117
+ echo "run-id=$ci_run_id"
95
118
  } >> "$GITHUB_OUTPUT"
96
119
 
97
- echo "PR #$PR_NUMBER closes #$issue; CI concluded $CONCLUSION"
120
+ echo "PR #$PR_NUMBER closes #$issue; CI concluded $ci_conclusion"
@@ -0,0 +1,81 @@
1
+ # Human adds implement/refine/direct label → validates permission → bot adds bot-working
2
+ # This ensures the bot is the actor for all agentic workflows.
3
+ name: "Authorize Bot Work"
4
+
5
+ on:
6
+ issues:
7
+ types: [labeled]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ authorize:
14
+ # Only trigger for implement/refine/direct labels, and only if bot-working is NOT already present
15
+ if: >
16
+ (github.event.label.name == 'implement' ||
17
+ github.event.label.name == 'refine' ||
18
+ github.event.label.name == 'direct') &&
19
+ !contains(github.event.issue.labels.*.name, 'bot-working')
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 5
22
+ permissions:
23
+ contents: read
24
+ issues: write
25
+ steps:
26
+ - name: Check actor permission
27
+ id: check
28
+ env:
29
+ GH_TOKEN: ${{ github.token }}
30
+ ACTOR: ${{ github.actor }}
31
+ run: |
32
+ set -euo pipefail
33
+
34
+ # Bots don't need validation - they're already authorized
35
+ if [[ "$ACTOR" == *"[bot]"* ]]; then
36
+ echo "authorized=true" >> "$GITHUB_OUTPUT"
37
+ echo "::notice::Bot actor ($ACTOR) - skipping permission check"
38
+ exit 0
39
+ fi
40
+
41
+ permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \
42
+ --jq '.permission' 2>/dev/null || echo "none")
43
+
44
+ case "$permission" in
45
+ admin | maintain | write)
46
+ echo "authorized=true" >> "$GITHUB_OUTPUT"
47
+ echo "::notice::$ACTOR has $permission permission - authorized"
48
+ ;;
49
+ *)
50
+ echo "authorized=false" >> "$GITHUB_OUTPUT"
51
+ echo "::warning::$ACTOR has '$permission' permission - not authorized"
52
+ ;;
53
+ esac
54
+
55
+ - name: Checkout for App token action
56
+ if: steps.check.outputs.authorized == 'true'
57
+ uses: actions/checkout@v4
58
+ with:
59
+ persist-credentials: false
60
+
61
+ - name: Generate App token
62
+ if: steps.check.outputs.authorized == 'true'
63
+ id: app-token
64
+ uses: actions/create-github-app-token@v1
65
+ with:
66
+ app-id: ${{ secrets.BOT_APP_ID }}
67
+ private-key: ${{ secrets.BOT_PRIVATE_KEY }}
68
+
69
+ - name: Bot adds bot-working label
70
+ if: steps.check.outputs.authorized == 'true'
71
+ env:
72
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
73
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
74
+ LABEL: ${{ github.event.label.name }}
75
+ run: |
76
+ set -euo pipefail
77
+
78
+ echo "Adding bot-working label to issue #$ISSUE_NUMBER (triggered by $LABEL)"
79
+ gh issue edit "$ISSUE_NUMBER" --add-label "bot-working"
80
+
81
+ echo "::notice::bot-working label added - bot will now own the $LABEL workflow"
@@ -37,7 +37,7 @@ on:
37
37
  types: [submitted]
38
38
 
39
39
  pull_request_target:
40
- types: [opened, synchronize, reopened]
40
+ types: [opened, synchronize, reopened, labeled]
41
41
  workflow_run:
42
42
  workflows: ["App: CI", "CI"]
43
43
  types: [completed]
@@ -54,7 +54,7 @@ on:
54
54
  - cron: "17 1 * * 1"
55
55
  - cron: "43 3 * * *"
56
56
  - cron: "0 6 * * *"
57
- - cron: "*/5 * * * *"
57
+ - cron: "*/15 * * * *"
58
58
  - cron: "0 */2 * * *"
59
59
  - cron: "29 7 * * *"
60
60
 
@@ -356,19 +356,25 @@ jobs:
356
356
  runs-on: ubuntu-latest
357
357
  timeout-minutes: 5
358
358
  permissions:
359
+ contents: read
359
360
  actions: write
360
361
  pull-requests: read
362
+ env:
363
+ BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
364
+ BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
361
365
  steps:
362
- # Runs only the default-branch workflow and never checks out pull-request code.
363
- # External users cannot impersonate github-actions[bot].
364
- - name: Create Platform bot token
366
+ - name: Checkout for App token action
367
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
368
+ with:
369
+ persist-credentials: false
370
+ - name: Generate App token
365
371
  id: app-token
366
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
372
+ uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
367
373
  with:
368
- client-id: ${{ secrets.BOT_APP_ID }}
374
+ app-id: ${{ secrets.BOT_APP_ID }}
369
375
  private-key: ${{ secrets.BOT_PRIVATE_KEY }}
370
- permission-actions: write
371
- permission-pull-requests: read
376
+ # Runs only the default-branch workflow and never checks out pull-request code.
377
+ # External users cannot impersonate github-actions[bot].
372
378
  - name: Approve checks and dispatch Merge Gate for trusted bot pull requests
373
379
  env:
374
380
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
@@ -383,6 +389,7 @@ jobs:
383
389
  | select(
384
390
  .user.login == "github-actions[bot]"
385
391
  and .head.repo.full_name == $repo
392
+ and .mergeable_state != "dirty"
386
393
  )
387
394
  | [.number, .head.ref] | @tsv
388
395
  ' |
@@ -427,6 +434,7 @@ jobs:
427
434
  -f "inputs[pr-number]=$pr_number" \
428
435
  -f "inputs[ci-conclusion]=$ci_conclusion" \
429
436
  -f "inputs[ci-run-id]=$ci_run_id"
437
+ done
430
438
 
431
439
  audit-close:
432
440
  needs: classify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",