cpflow 5.2.0 → 6.0.0.rc.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/agent-workflow.yml +26 -2
  3. data/.agents/bin/README.md +16 -4
  4. data/.agents/bin/docs +1 -1
  5. data/.agents/bin/lint +1 -1
  6. data/.agents/bin/setup +1 -1
  7. data/.agents/bin/test +1 -1
  8. data/.agents/bin/validate +2 -1
  9. data/.coderabbit.yaml +13 -0
  10. data/.github/actions/cpflow-delete-control-plane-app/action.yml +2 -1
  11. data/.github/actions/cpflow-setup-environment/action.yml +9 -7
  12. data/.github/actions/cpflow-wait-for-health/action.yml +87 -15
  13. data/.github/dependabot.yml +10 -0
  14. data/.github/pull_request_template.md +18 -0
  15. data/.github/workflows/check_cpln_links.yml +6 -1
  16. data/.github/workflows/claude-code-review.yml +5 -2
  17. data/.github/workflows/claude.yml +97 -4
  18. data/.github/workflows/coderabbit-lifecycle-review.yml +29 -0
  19. data/.github/workflows/command_docs.yml +7 -2
  20. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +12 -5
  21. data/.github/workflows/cpflow-delete-review-app.yml +638 -43
  22. data/.github/workflows/cpflow-deploy-review-app.yml +665 -36
  23. data/.github/workflows/cpflow-deploy-staging.yml +16 -10
  24. data/.github/workflows/cpflow-help-command.yml +3 -3
  25. data/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
  26. data/.github/workflows/cpflow-review-app-help.yml +6 -14
  27. data/.github/workflows/rspec-shared.yml +34 -26
  28. data/.github/workflows/rspec-specific.yml +10 -2
  29. data/.github/workflows/rspec.yml +71 -4
  30. data/.github/workflows/rubocop.yml +9 -2
  31. data/.github/workflows/trigger-docs-site.yml +2 -0
  32. data/.rubocop.yml +6 -1
  33. data/AGENTS.md +6 -49
  34. data/CHANGELOG.md +56 -1
  35. data/CONTRIBUTING.md +48 -3
  36. data/Gemfile.lock +1 -1
  37. data/README.md +6 -1
  38. data/cpflow.gemspec +3 -15
  39. data/docs/ai-github-flow-prompt.md +2 -2
  40. data/docs/ci-automation.md +216 -99
  41. data/docs/commands.md +36 -9
  42. data/docs/rds-private-networking.md +12 -12
  43. data/docs/releasing.md +15 -4
  44. data/docs/secrets-and-env-values.md +8 -0
  45. data/docs/tips.md +18 -0
  46. data/examples/controlplane.yml +5 -0
  47. data/lib/command/ai_github_flow_prompt.rb +1 -1
  48. data/lib/command/apply_template.rb +104 -2
  49. data/lib/command/base.rb +52 -3
  50. data/lib/command/cleanup_stale_apps.rb +28 -4
  51. data/lib/command/copy_image_from_upstream.rb +3 -0
  52. data/lib/command/deploy_image.rb +54 -3
  53. data/lib/command/generate_github_actions.rb +36 -12
  54. data/lib/command/run.rb +357 -44
  55. data/lib/command/setup_app.rb +10 -5
  56. data/lib/command/update_github_actions.rb +7 -6
  57. data/lib/core/controlplane.rb +179 -79
  58. data/lib/core/controlplane_api.rb +8 -0
  59. data/lib/core/controlplane_api_direct.rb +257 -63
  60. data/lib/core/shell.rb +39 -7
  61. data/lib/core/timed_command.rb +111 -0
  62. data/lib/cpflow/version.rb +1 -1
  63. data/lib/cpflow.rb +1 -1
  64. data/lib/github_flow_templates/.github/cpflow-help.md +24 -10
  65. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +10 -0
  66. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +9 -0
  67. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
  68. data/lib/github_flow_templates/bin/test-cpflow-github-flow +63 -3
  69. data/lib/patches/hash.rb +2 -2
  70. data/rakelib/create_release.rake +14 -14
  71. data/script/check_shell_scripts +66 -0
  72. metadata +12 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d334d4f24d777236b1d99232c0aeb14aaac17a3daed74837738773a5aa42b3c
4
- data.tar.gz: ca7b66e6bd876a6a3f422a425aa86ce90e74af5f57e23eada8384c3f1e963f75
3
+ metadata.gz: 73c78e337b3728ba8273db59f8f7d9540e855e986ba7aed509c3091a7cf806dd
4
+ data.tar.gz: d399af718dd0ab5a103d13b2eb7bc3515d2839077280254c29563fa88f14559d
5
5
  SHA512:
6
- metadata.gz: e72015fc6143701ee31e311030a71c91957556aba0cc762c01d63834cd71d01ba7bfe4e76e216700a8367e365fbb3da2459c70707e6364e7985326e00e6ccab5
7
- data.tar.gz: 4b19772575225b90d96ebe788cc555699210df4aa9ae92677e6fbde395f4bcc92b2ef2206b062049c8192ca91228e45e7c7c9414b1948b896248758eb8c71370
6
+ metadata.gz: ad64de2a50dac54f6c9ee541ec8a2e80f13a36b81cedc2c58924c881ed80df8ff5f6c231255cae7328fbeeda80992e98fbab613f406f149478e7be2711a5a958
7
+ data.tar.gz: 927713a8b5e829ec6c89aef86ba9020d686bf6b1647473efbb356f31f9ca590185b8c582c061afd555ad32efa6f6caf38c834fba03cf56a52b2a7970f239f2db
@@ -1,8 +1,10 @@
1
1
  # Non-command agent-workflow configuration for portable shared skills.
2
+ # This file is the canonical source for the non-command policy values below.
3
+ # AGENTS.md directs portable skills here; it does not duplicate or override them.
2
4
  # Commands live as scripts in .agents/bin/ (see .agents/bin/README.md).
3
- # Compatibility summary for older workflow copies; canonical values live in
4
- # AGENTS.md.
5
5
  base_branch: main
6
+ merge_submission:
7
+ mode: direct
6
8
  changelog: "CHANGELOG.md — Keep-a-Changelog; user-visible changes only"
7
9
  follow_up_prefix: "Follow-up:"
8
10
  review_gate: "AI reviewers are advisory unless they confirm a blocker; merge gate is the full `gh pr checks` list green (not --required) + all threads resolved + mergeable clean"
@@ -11,5 +13,27 @@ coordination_backend: "private shakacode/agent-coordination (claims/heartbeats n
11
13
  benchmark_labels: n/a
12
14
  merge_ledger: n/a
13
15
  ci_parity_environment: "n/a — reproduce CI-only failures from the matching job in .github/workflows/**"
16
+ release_qa_runbook: ".agents/workflows/ai-rollout-e2e-test.md — use after publishing a cpflow gem that changes GitHub Actions, AI rollout prompts, readiness checks, generator output, or React on Rails deployment behavior"
14
17
  hosted_ci_trigger: "n/a — CI runs on every PR"
15
18
  ci_change_detector: n/a
19
+
20
+ # Consumed by the portable untrusted-contributor-intake skill. These values must
21
+ # be read from the trusted base ref, never from contributor-controlled PR content.
22
+ untrusted_contributor_intake:
23
+ trusted_github_host: github.com
24
+ trusted_github_scheme: https
25
+ trusted_github_repo: shakacode/control-plane-flow
26
+ repo_prefix: CPF
27
+
28
+ # External actions already used by this repository and reviewed for the closed
29
+ # allowlist enforced by the GitHub Actions security scanner. Every use still
30
+ # requires an immutable commit SHA and a same-line release-version comment.
31
+ trusted_actions:
32
+ - actions/checkout
33
+ - actions/create-github-app-token
34
+ - actions/github-script
35
+ - actions/upload-artifact
36
+ - anthropics/claude-code-action
37
+ - docker/setup-buildx-action
38
+ - peter-evans/repository-dispatch
39
+ - ruby/setup-ruby
@@ -8,12 +8,24 @@ means that capability is n/a here.
8
8
  | Script | Purpose | This repo runs |
9
9
  | --- | --- | --- |
10
10
  | `setup` | Install dependencies | `bundle install` |
11
- | `validate` | Pre-push gate (run before pushing) | `bundle exec rake` (rspec + rubocop) |
11
+ | `validate` | Pre-push gate (run before pushing) | ShellCheck, then `bundle exec rake` (rspec + rubocop) |
12
12
  | `test` | Run tests | `bundle exec rspec` |
13
13
  | `lint` | Lint / format (pass `-A` to fix) | `bundle exec rubocop` |
14
14
  | `docs` | Check generated command docs | `bundle exec rake check_command_docs` |
15
15
  | `build` | Build / type-check | n/a (gem) |
16
16
 
17
- Canonical non-command policy lives in [`../../AGENTS.md`](../../AGENTS.md).
18
- [`../agent-workflow.yml`](../agent-workflow.yml) is retained only as a
19
- compatibility summary for older workflow copies.
17
+ `validate` requires ShellCheck to be installed and available as `shellcheck` on
18
+ `PATH`.
19
+
20
+ `script/check_shell_scripts` checks every Git-tracked `.sh` and `.bash` file,
21
+ plus the extensionless shell entrypoints declared in its
22
+ `extensionless_shell_files` array. It does not infer script languages from
23
+ shebangs. When adding, renaming, or removing an extensionless shell script,
24
+ update that array and its inventory regression test; use a `.sh` or `.bash`
25
+ suffix for automatic inclusion. Declared paths must remain tracked and readable.
26
+ Unlisted extensionless files and non-shell files are outside this check.
27
+
28
+ Canonical non-command policy, including the release-QA runbook reference, lives
29
+ in [`../agent-workflow.yml`](../agent-workflow.yml). [`../../AGENTS.md`](../../AGENTS.md)
30
+ is the thin discovery pointer for portable shared skills and does not duplicate
31
+ or override that policy.
data/.agents/bin/docs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  # Check generated command docs are current.
3
3
  set -euo pipefail
4
- cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
4
+ cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
5
  exec bundle exec rake check_command_docs
data/.agents/bin/lint CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  # Lint / format check. Pass -A to autocorrect.
3
3
  set -euo pipefail
4
- cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
4
+ cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
5
  exec bundle exec rubocop "$@"
data/.agents/bin/setup CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  # Install dependencies.
3
3
  set -euo pipefail
4
- cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
4
+ cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
5
  exec bundle install
data/.agents/bin/test CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  # Run the test suite. Extra args pass through to rspec.
3
3
  set -euo pipefail
4
- cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
4
+ cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
5
  exec bundle exec rspec "$@"
data/.agents/bin/validate CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Pre-push gate: full local validation (run before pushing).
3
3
  set -euo pipefail
4
- cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
4
+ cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
+ ./script/check_shell_scripts
5
6
  exec bundle exec rake
data/.coderabbit.yaml ADDED
@@ -0,0 +1,13 @@
1
+ reviews:
2
+ profile: chill
3
+ review_status: false
4
+ review_progress: false
5
+ commit_status: false
6
+ in_progress_fortune: false
7
+ collapse_walkthrough: true
8
+ auto_review:
9
+ enabled: false
10
+ auto_incremental_review: false
11
+ labels:
12
+ - coderabbit:first-pass
13
+ - coderabbit:ready
@@ -22,8 +22,9 @@ runs:
22
22
  - name: Delete application
23
23
  shell: bash
24
24
  working-directory: ${{ inputs.working_directory }}
25
- run: ${{ github.action_path }}/delete-app.sh
25
+ run: "${ACTION_PATH}/delete-app.sh"
26
26
  env:
27
+ ACTION_PATH: ${{ github.action_path }}
27
28
  APP_NAME: ${{ inputs.app_name }}
28
29
  CPLN_ORG: ${{ inputs.cpln_org }}
29
30
  REVIEW_APP_PREFIX: ${{ inputs.review_app_prefix }}
@@ -19,6 +19,10 @@ inputs:
19
19
  description: Directory where ruby/setup-ruby should detect Ruby version files.
20
20
  required: false
21
21
  default: "."
22
+ cpflow_source_directory:
23
+ description: Directory containing the checked-out control-plane-flow source.
24
+ required: false
25
+ default: ".cpflow"
22
26
  # GitHub parses double-brace expression snippets inside action metadata (including
23
27
  # `description:`) while loading the composite action, and the `vars` context is not
24
28
  # available in that phase. Keep these descriptions in plain prose - reference repo
@@ -47,8 +51,8 @@ inputs:
47
51
 
48
52
  runs:
49
53
  using: composite
50
- # ruby/setup-ruby tracks Ruby/toolcache updates on a major tag. The privileged
51
- # reusable workflows pin GitHub-owned actions to immutable SHAs.
54
+ # Dependabot proposes action updates, but each reviewed dependency remains
55
+ # pinned to the release commit recorded beside its exact version comment.
52
56
  steps:
53
57
  - name: Resolve Ruby setup version
54
58
  id: ruby-version
@@ -80,9 +84,7 @@ runs:
80
84
  echo "ruby_version=${ruby_version}" >> "$GITHUB_OUTPUT"
81
85
 
82
86
  - name: Set up Ruby
83
- # ruby/setup-ruby intentionally tracks the v1 tag so Ruby/toolcache updates
84
- # roll out automatically; Dependabot/Renovate can manage this non-GitHub action.
85
- uses: ruby/setup-ruby@v1
87
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
86
88
  with:
87
89
  ruby-version: ${{ steps.ruby-version.outputs.ruby_version }}
88
90
  working-directory: ${{ inputs.working_directory }}
@@ -93,13 +95,13 @@ runs:
93
95
  CONTROL_PLANE_FLOW_REF: ${{ inputs.control_plane_flow_ref }}
94
96
  CPLN_CLI_VERSION: ${{ inputs.cpln_cli_version }}
95
97
  CPFLOW_VERSION: ${{ inputs.cpflow_version }}
96
- CPFLOW_SOURCE_DIR: ${{ github.action_path }}/../../..
98
+ CPFLOW_SOURCE_DIR: ${{ inputs.cpflow_source_directory }}
97
99
  run: |
98
100
  set -euo pipefail
99
101
 
100
102
  # Bump this default when a new Control Plane CLI release should roll out by default.
101
103
  # Override per-repo by setting the `CPLN_CLI_VERSION` repo variable.
102
- default_cpln_cli_version="3.10.2"
104
+ default_cpln_cli_version="3.11.0"
103
105
 
104
106
  CPLN_CLI_VERSION="${CPLN_CLI_VERSION:-${default_cpln_cli_version}}"
105
107
 
@@ -1,9 +1,9 @@
1
1
  name: Wait for Control Plane workload health
2
2
  description: >-
3
3
  Polls Control Plane until the latest workload version is ready, then checks
4
- the workload endpoint with curl. Exits success when the HTTP response status
5
- is in the accepted list. Fails non-zero (and reports `healthy=false`) once
6
- retries are exhausted.
4
+ the standard workload endpoint and ready location endpoints with curl. Exits
5
+ success when the HTTP response status is in the accepted list. Fails non-zero
6
+ (and reports `healthy=false`) once retries are exhausted.
7
7
 
8
8
  inputs:
9
9
  workload_name:
@@ -39,6 +39,9 @@ outputs:
39
39
  healthy:
40
40
  description: '"true" once a healthy response was observed; "false" otherwise.'
41
41
  value: ${{ steps.poll.outputs.healthy }}
42
+ endpoint:
43
+ description: The workload or location endpoint that returned an accepted status.
44
+ value: ${{ steps.poll.outputs.endpoint }}
42
45
 
43
46
  runs:
44
47
  using: composite
@@ -59,6 +62,40 @@ runs:
59
62
 
60
63
  read -r -a accepted_statuses <<< "${CPFLOW_ACCEPTED_STATUSES}"
61
64
 
65
+ check_endpoint() {
66
+ local endpoint="$1"
67
+ local endpoint_type="$2"
68
+ local http_status
69
+
70
+ http_status="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${CPFLOW_CURL_MAX_TIME}" "${endpoint}" 2>/dev/null || true)"
71
+ http_status="${http_status:-000}"
72
+ echo "${endpoint_type}: ${endpoint}, HTTP status: ${http_status}"
73
+
74
+ for accepted in "${accepted_statuses[@]}"; do
75
+ if [[ "${http_status}" == "${accepted}" ]]; then
76
+ {
77
+ echo "healthy=true"
78
+ echo "endpoint=${endpoint}"
79
+ } >> "$GITHUB_OUTPUT"
80
+ return 0
81
+ fi
82
+ done
83
+
84
+ return 1
85
+ }
86
+
87
+ summarize_cpln_error() {
88
+ local error_text="$1"
89
+
90
+ case "${error_text}" in
91
+ *401*|*403*|*[Uu]nauthorized*|*[Ff]orbidden*) echo "authorization failure" ;;
92
+ *404*|*"not found"*|*"Not Found"*) echo "resource not found" ;;
93
+ *429*|*"rate limit"*|*"Rate Limit"*) echo "rate limited" ;;
94
+ *"timed out"*|*[Tt]imeout*) echo "request timeout" ;;
95
+ *) echo "CLI error (details suppressed; ${#error_text} stderr bytes captured)" ;;
96
+ esac
97
+ }
98
+
62
99
  for attempt in $(seq 1 "${CPFLOW_MAX_RETRIES}"); do
63
100
  echo "Health check attempt ${attempt}/${CPFLOW_MAX_RETRIES}"
64
101
 
@@ -72,22 +109,57 @@ runs:
72
109
  workload_ready="$(echo "${workload_json}" | jq -r '.status.ready // false')"
73
110
  latest_ready="$(echo "${workload_json}" | jq -r '.status.readyLatest // false')"
74
111
  readiness_status="$(echo "${workload_json}" | jq -r '.health.readiness // "unknown"')"
75
- endpoint="$(echo "${workload_json}" | jq -r '.status.endpoint // empty')"
112
+ workload_endpoint="$(echo "${workload_json}" | jq -r '.status.endpoint // empty')"
76
113
 
77
114
  if [[ "${workload_ready}" != "true" || "${latest_ready}" != "true" ]]; then
78
115
  echo "Workload status: ready=${workload_ready}, readyLatest=${latest_ready}, readiness=${readiness_status}; waiting for latest deployment."
79
- elif [[ -n "${endpoint}" ]]; then
80
- http_status="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${CPFLOW_CURL_MAX_TIME}" "${endpoint}" 2>/dev/null || echo 000)"
81
- echo "Endpoint: ${endpoint}, HTTP status: ${http_status}"
82
-
83
- for accepted in "${accepted_statuses[@]}"; do
84
- if [[ "${http_status}" == "${accepted}" ]]; then
85
- echo "healthy=true" >> "$GITHUB_OUTPUT"
86
- exit 0
87
- fi
88
- done
89
116
  else
90
- echo "Workload '${CPFLOW_WORKLOAD_NAME}' has no endpoint yet; waiting for one to be assigned."
117
+ if [[ -n "${workload_endpoint}" ]] && check_endpoint "${workload_endpoint}" "Workload endpoint"; then
118
+ exit 0
119
+ fi
120
+
121
+ location_endpoint_count=0
122
+ deployments_listed=false
123
+ deployments_settled=false
124
+ deployments_stderr="$(mktemp)"
125
+ if deployments_json="$(cpln workload get-deployments "${CPFLOW_WORKLOAD_NAME}" --gvc "${CPFLOW_APP_NAME}" --org "${CPFLOW_ORG}" -o json 2>"${deployments_stderr}")"; then
126
+ deployments_listed=true
127
+ unsettled_location_count="$(
128
+ printf '%s\n' "${deployments_json}" |
129
+ jq '[.items[]? | select(.status.ready != true or (.status.deploying // false) == true)] | length'
130
+ )"
131
+
132
+ if [[ "${unsettled_location_count}" -gt 0 ]]; then
133
+ echo "Deployment locations are not all ready (${unsettled_location_count} unsettled); waiting before endpoint fallback."
134
+ else
135
+ deployments_settled=true
136
+ while IFS= read -r location_endpoint; do
137
+ location_endpoint_count=$((location_endpoint_count + 1))
138
+ if [[ -n "${location_endpoint}" &&
139
+ "${location_endpoint}" != "${workload_endpoint}" ]] &&
140
+ check_endpoint "${location_endpoint}" "Location endpoint"; then
141
+ rm -f "${deployments_stderr}"
142
+ exit 0
143
+ fi
144
+ done < <(
145
+ printf '%s\n' "${deployments_json}" |
146
+ jq -r '.items[]? | .status.endpoint // empty' |
147
+ awk '!seen[$0]++'
148
+ )
149
+ fi
150
+ else
151
+ deployments_error="$(<"${deployments_stderr}")"
152
+ deployments_error_summary="$(summarize_cpln_error "${deployments_error}")"
153
+ echo "::warning::Could not list location endpoints for workload '${CPFLOW_WORKLOAD_NAME}' (${deployments_error_summary}); retrying."
154
+ fi
155
+ rm -f "${deployments_stderr}"
156
+
157
+ if [[ -z "${workload_endpoint}" &&
158
+ "${deployments_listed}" == "true" &&
159
+ "${deployments_settled}" == "true" &&
160
+ "${location_endpoint_count}" -eq 0 ]]; then
161
+ echo "Workload '${CPFLOW_WORKLOAD_NAME}' has no endpoint yet; waiting for one to be assigned."
162
+ fi
91
163
  fi
92
164
 
93
165
  if [[ "${attempt}" -lt "${CPFLOW_MAX_RETRIES}" ]]; then
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ - package-ecosystem: github-actions
8
+ directory: "/.github/actions/cpflow-setup-environment"
9
+ schedule:
10
+ interval: weekly
@@ -0,0 +1,18 @@
1
+ ## Summary
2
+
3
+ <!-- What changes, and why is it useful? -->
4
+
5
+ ## Related issue
6
+
7
+ <!-- Use "Closes #123" for an accepted issue, or link the maintainer discussion. -->
8
+
9
+ ## Validation
10
+
11
+ <!-- List the exact commands or manual checks you ran. -->
12
+
13
+ ## Checklist
14
+
15
+ - [ ] This PR addresses an accepted issue or maintainer request, or is a small typo fix.
16
+ - [ ] The change is focused and contains no unrelated cleanup or generated churn.
17
+ - [ ] I added or updated tests, documentation, and `CHANGELOG.md` where applicable.
18
+ - [ ] I reviewed and understand all submitted content, including AI-assisted changes.
@@ -6,13 +6,18 @@ on:
6
6
  - main
7
7
  pull_request:
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  jobs:
10
13
  check_cpln_links:
11
14
  runs-on: ubuntu-latest
12
15
  name: Check Links
13
16
  steps:
14
17
  - name: Checkout repository
15
- uses: actions/checkout@v3
18
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
16
21
  - name: Validate outgoing links to Control Plane
17
22
  run: |
18
23
  TERM=xterm-color ./script/check_cpln_links
@@ -4,6 +4,8 @@ on:
4
4
  pull_request:
5
5
  types: [opened, synchronize, ready_for_review, reopened]
6
6
 
7
+ permissions: {}
8
+
7
9
  jobs:
8
10
  claude-review:
9
11
  runs-on: ubuntu-latest
@@ -15,13 +17,14 @@ jobs:
15
17
 
16
18
  steps:
17
19
  - name: Checkout repository
18
- uses: actions/checkout@v6
20
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
21
  with:
20
22
  fetch-depth: 1
23
+ persist-credentials: false
21
24
 
22
25
  - name: Run Claude Code Review
23
26
  id: claude-review
24
- uses: anthropics/claude-code-action@v1
27
+ uses: anthropics/claude-code-action@8251c103ac8c1d761882c86aba1412c7f583c844 # v1.0.213
25
28
  env:
26
29
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
27
30
  with:
@@ -10,14 +10,107 @@ on:
10
10
  pull_request_review:
11
11
  types: [submitted]
12
12
 
13
+ permissions: {}
14
+
13
15
  jobs:
14
- claude:
16
+ authorize_claude_actor:
15
17
  if: |
16
18
  (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
19
  (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
20
  (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
21
  (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
22
  runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ outputs:
26
+ authorized: ${{ steps.permission-gate.outputs.authorized }}
27
+ steps:
28
+ - name: Verify actor can run Claude
29
+ id: permission-gate
30
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
31
+ with:
32
+ script: |
33
+ const owner = context.repo.owner;
34
+ const repo = context.repo.repo;
35
+ const permissionByLogin = new Map();
36
+
37
+ function claudeRequester() {
38
+ if (
39
+ context.eventName === 'issue_comment' ||
40
+ context.eventName === 'pull_request_review_comment'
41
+ ) {
42
+ return context.payload.comment?.user?.login;
43
+ }
44
+
45
+ if (context.eventName === 'pull_request_review') {
46
+ return context.payload.review?.user?.login;
47
+ }
48
+
49
+ if (context.eventName === 'issues') {
50
+ return context.payload.issue?.user?.login;
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ async function hasWriteAccessFor(username) {
57
+ if (!username) {
58
+ return false;
59
+ }
60
+
61
+ if (permissionByLogin.has(username)) {
62
+ return permissionByLogin.get(username);
63
+ }
64
+
65
+ let hasWriteAccess = false;
66
+ try {
67
+ const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
68
+ owner,
69
+ repo,
70
+ username
71
+ });
72
+ hasWriteAccess = ['admin', 'write'].includes(permission.permission);
73
+ } catch (error) {
74
+ core.warning(
75
+ `Unable to verify ${username} write permission for Claude; ` +
76
+ `treating the Claude request as untrusted: ${error.message || error}`
77
+ );
78
+ }
79
+
80
+ permissionByLogin.set(username, hasWriteAccess);
81
+ return hasWriteAccess;
82
+ }
83
+
84
+ const actor = context.actor;
85
+ const requester = claudeRequester();
86
+
87
+ if (!requester) {
88
+ core.setOutput('authorized', 'false');
89
+ core.setFailed('Unable to run Claude because the requester could not be determined.');
90
+ return;
91
+ }
92
+
93
+ if (!(await hasWriteAccessFor(actor))) {
94
+ core.setOutput('authorized', 'false');
95
+ core.setFailed(`Unable to run Claude because ${actor} does not have write/admin repository permission.`);
96
+ return;
97
+ }
98
+
99
+ if (!(await hasWriteAccessFor(requester))) {
100
+ core.setOutput('authorized', 'false');
101
+ core.setFailed(
102
+ `Unable to run Claude because requester ${requester} does not have write/admin repository permission.`
103
+ );
104
+ return;
105
+ }
106
+
107
+ core.setOutput('authorized', 'true');
108
+ core.info(`Authorized ${actor} and Claude requester ${requester} to run Claude.`);
109
+
110
+ claude:
111
+ needs: authorize_claude_actor
112
+ if: needs.authorize_claude_actor.outputs.authorized == 'true'
113
+ runs-on: ubuntu-latest
21
114
  permissions:
22
115
  contents: read
23
116
  pull-requests: read
@@ -28,13 +121,14 @@ jobs:
28
121
  statuses: read
29
122
  steps:
30
123
  - name: Checkout repository
31
- uses: actions/checkout@v4
124
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
125
  with:
33
126
  fetch-depth: 1
127
+ persist-credentials: false
34
128
 
35
129
  - name: Run Claude Code
36
130
  id: claude
37
- uses: anthropics/claude-code-action@v1
131
+ uses: anthropics/claude-code-action@8251c103ac8c1d761882c86aba1412c7f583c844 # v1.0.213
38
132
  env:
39
133
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
40
134
  with:
@@ -54,4 +148,3 @@ jobs:
54
148
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
55
149
  # or https://code.claude.com/docs/en/cli-reference for available options
56
150
  # claude_args: '--allowed-tools Bash(gh pr:*)'
57
-
@@ -0,0 +1,29 @@
1
+ name: CodeRabbit Lifecycle Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, ready_for_review]
6
+
7
+ permissions:
8
+ pull-requests: write
9
+
10
+ jobs:
11
+ request-review:
12
+ if: github.event.pull_request.head.repo.fork == false
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Add CodeRabbit lifecycle label
16
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
17
+ with:
18
+ # Both labels must already exist in the repo (addLabels does not create them); created 2026-09-06.
19
+ script: |
20
+ const label = context.payload.action === 'opened'
21
+ ? 'coderabbit:first-pass'
22
+ : 'coderabbit:ready';
23
+
24
+ await github.rest.issues.addLabels({
25
+ owner: context.repo.owner,
26
+ repo: context.repo.repo,
27
+ issue_number: context.issue.number,
28
+ labels: [label]
29
+ });
@@ -6,15 +6,20 @@ on:
6
6
  - main
7
7
  pull_request:
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  jobs:
10
13
  rspec:
11
14
  runs-on: ubuntu-latest
12
15
  name: Command Docs
13
16
  steps:
14
17
  - name: Checkout code
15
- uses: actions/checkout@v3
18
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
16
21
  - name: Set up Ruby
17
- uses: ruby/setup-ruby@v1
22
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
18
23
  with:
19
24
  ruby-version: "3.2"
20
25
  bundler-cache: true
@@ -23,8 +23,15 @@ jobs:
23
23
  runs-on: ubuntu-latest
24
24
  timeout-minutes: 30
25
25
  steps:
26
+ # No explicit ref: checkout's default resolves to this event's recorded commit
27
+ # (GITHUB_SHA), which is trusted base-repository code and is exempt from checkout v7's fork-PR guard.
28
+ - name: Checkout generated cpflow actions
29
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
+ with:
31
+ persist-credentials: false
32
+
26
33
  - name: Checkout control-plane-flow actions
27
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
34
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
35
  with:
29
36
  repository: ${{ job.workflow_repository }}
30
37
  ref: ${{ job.workflow_sha }}
@@ -32,7 +39,7 @@ jobs:
32
39
  persist-credentials: false
33
40
 
34
41
  - name: Validate required secrets and variables
35
- uses: ./.cpflow/.github/actions/cpflow-validate-config
42
+ uses: ./.github/actions/cpflow-validate-config
36
43
  env:
37
44
  CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
38
45
  with:
@@ -40,21 +47,21 @@ jobs:
40
47
  secret:CPLN_TOKEN_STAGING
41
48
 
42
49
  - name: Checkout caller repository
43
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
50
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
51
  with:
45
52
  path: app
46
53
  persist-credentials: false
47
54
 
48
55
  - name: Resolve review app config
49
56
  id: review-config
50
- uses: ./.cpflow/.github/actions/cpflow-resolve-review-config
57
+ uses: ./.github/actions/cpflow-resolve-review-config
51
58
  with:
52
59
  working_directory: app
53
60
  configured_cpln_org_staging: ${{ vars.CPLN_ORG_STAGING }}
54
61
  configured_review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
55
62
 
56
63
  - name: Setup environment
57
- uses: ./.cpflow/.github/actions/cpflow-setup-environment
64
+ uses: ./.github/actions/cpflow-setup-environment
58
65
  with:
59
66
  token: ${{ secrets.CPLN_TOKEN_STAGING }}
60
67
  org: ${{ steps.review-config.outputs.cpln_org }}