@plainconceptsplatform/workflows 0.28.12 → 0.28.14

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.
@@ -1,260 +1,260 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
2
- name: "App: CI (Node monorepo)"
3
-
4
- on:
5
- pull_request:
6
- schedule:
7
- - cron: "0 6 * * 1"
8
- workflow_dispatch:
9
-
10
- env:
11
- NODE_VERSION: "22"
12
- PNPM_VERSION: "10"
13
- WEB_PACKAGE: "@app/web"
14
- DESKTOP_PACKAGE: "@app/desktop"
15
- MOBILE_PACKAGE: "@app/mobile"
16
- WEB_EXPORT_PATH: apps/web/out
17
- PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
18
- DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
19
- DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
20
- DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
21
- MOBILE_IOS_PATH: apps/mobile/ios
22
- MOBILE_ANDROID_PATH: apps/mobile/android
23
- WEB_ARTIFACT_NAME: web-export
24
- PLAYWRIGHT_ARTIFACT_NAME: playwright-report
25
- TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
26
- SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
27
- SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
28
- MUTATION_THRESHOLD: "60"
29
-
30
- permissions:
31
- contents: read
32
-
33
- concurrency:
34
- group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
35
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
36
-
37
- jobs:
38
- lint:
39
- name: Lint (Biome)
40
- if: github.event_name != 'schedule'
41
- runs-on: RunnerLandingZone
42
- steps:
43
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
45
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
46
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
47
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
48
- - run: pnpm install --frozen-lockfile
49
- - run: pnpm exec biome check .
50
-
51
- web:
52
- name: Web (Next.js static export)
53
- if: github.event_name != 'schedule'
54
- runs-on: RunnerLandingZone
55
- steps:
56
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
58
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
59
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
60
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
61
- - run: pnpm install --frozen-lockfile
62
- - run: pnpm --filter "$WEB_PACKAGE" run typecheck
63
- - run: pnpm --filter "$WEB_PACKAGE" run test
64
- - run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
65
- - run: pnpm --filter "$WEB_PACKAGE" run build
66
- - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
67
- with:
68
- name: ${{ env.WEB_ARTIFACT_NAME }}
69
- path: ${{ env.WEB_EXPORT_PATH }}
70
- retention-days: 3
71
- if-no-files-found: error
72
-
73
- mutation-web:
74
- name: Mutation (web, diff)
75
- if: github.event_name == 'pull_request'
76
- runs-on: RunnerLandingZone
77
- timeout-minutes: 25
78
- steps:
79
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
80
- with:
81
- fetch-depth: 0
82
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
83
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
84
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
85
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
86
- - name: Detect web changes
87
- id: scope
88
- run: |
89
- git fetch origin "$GITHUB_BASE_REF" --depth=1
90
- if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
91
- echo "run=true" >> "$GITHUB_OUTPUT"
92
- else
93
- echo "run=false" >> "$GITHUB_OUTPUT"
94
- echo "No apps/web changes in this PR; skipping web mutation."
95
- fi
96
- - run: pnpm install --frozen-lockfile
97
- if: steps.scope.outputs.run == 'true'
98
- - name: StrykerJS (changed files only)
99
- if: steps.scope.outputs.run == 'true'
100
- run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
101
-
102
- e2e:
103
- name: E2E (Playwright)
104
- if: github.event_name != 'schedule'
105
- needs: web
106
- runs-on: RunnerLandingZone
107
- steps:
108
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
109
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
110
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
111
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
112
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
113
- - run: pnpm install --frozen-lockfile
114
- - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
115
- with:
116
- name: ${{ env.WEB_ARTIFACT_NAME }}
117
- path: ${{ env.WEB_EXPORT_PATH }}
118
- - run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
119
- - run: pnpm --filter "$WEB_PACKAGE" run e2e
120
- - if: failure()
121
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
122
- with:
123
- name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
124
- path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
125
- retention-days: 7
126
-
127
- desktop:
128
- name: Desktop (Electron)
129
- if: github.event_name != 'schedule'
130
- needs: web
131
- runs-on: RunnerLandingZone
132
- steps:
133
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
134
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
135
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
136
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
137
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
138
- - run: pnpm install --frozen-lockfile
139
- - run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
140
- - run: pnpm --filter "$DESKTOP_PACKAGE" run build
141
- - run: |
142
- test -f "$DESKTOP_MAIN_PATH"
143
- test -f "$DESKTOP_PRELOAD_PATH"
144
- grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
145
- - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
146
- with:
147
- name: ${{ env.WEB_ARTIFACT_NAME }}
148
- path: ${{ env.WEB_EXPORT_PATH }}
149
- - run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
150
- - run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
151
-
152
- mobile:
153
- name: Mobile (Capacitor)
154
- if: github.event_name != 'schedule'
155
- runs-on: RunnerLandingZone
156
- steps:
157
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
158
- - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
159
- with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
160
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
161
- with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
162
- - run: pnpm install --frozen-lockfile
163
- - run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
164
- - run: |
165
- if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
166
- pnpm --filter "$WEB_PACKAGE" run build
167
- pnpm --filter "$MOBILE_PACKAGE" run sync
168
- else
169
- echo "::notice::Native projects absent; skipping Capacitor sync."
170
- fi
171
-
172
- secret-scan:
173
- name: Secret scan (TruffleHog)
174
- runs-on: RunnerLandingZone
175
- steps:
176
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
177
- - run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
178
-
179
- deps-and-iac-scan:
180
- name: Dependencies and IaC (Trivy)
181
- runs-on: RunnerLandingZone
182
- steps:
183
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
184
- - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
185
- with:
186
- scan-type: fs
187
- scanners: vuln,misconfig,secret
188
- severity: CRITICAL,HIGH
189
- ignore-unfixed: true
190
- exit-code: "1"
191
- format: table
192
-
193
- sast-scan:
194
- name: SAST (Semgrep)
195
- runs-on: RunnerLandingZone
196
- steps:
197
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
198
- - run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
199
-
200
- sbom:
201
- name: SBOM (CycloneDX)
202
- runs-on: RunnerLandingZone
203
- steps:
204
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
205
- - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
206
- with:
207
- format: cyclonedx-json
208
- artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
209
-
210
- # A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
211
- # for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
212
- # never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
213
- # one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
214
- # router treats a dispatch as trusted. Human pull requests are left to the workflow_run
215
- # route, which does fire for them. The reconcile belt remains the hourly fallback.
216
- #
217
- # If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
218
- # `actions: write`. A called workflow may not request a scope its caller withheld, and the
219
- # whole run fails to start with zero jobs and no annotation when it does.
220
- dispatch-merge-gate:
221
- name: Dispatch merge gate
222
- needs: [lint, web, mutation-web, e2e, desktop, mobile]
223
- if: >
224
- always() &&
225
- github.event_name == 'pull_request' &&
226
- github.event.pull_request.head.repo.full_name == github.repository &&
227
- github.event.pull_request.draft == false &&
228
- github.event.pull_request.user.type == 'Bot'
229
- runs-on: RunnerLandingZone
230
- timeout-minutes: 5
231
- permissions:
232
- contents: read
233
- actions: write
234
- steps:
235
- - name: Dispatch the merge gate with this run's verdict
236
- env:
237
- GH_TOKEN: ${{ github.token }}
238
- REPO: ${{ github.repository }}
239
- REF: ${{ github.event.repository.default_branch }}
240
- PR_NUMBER: ${{ github.event.pull_request.number }}
241
- RUN_ID: ${{ github.run_id }}
242
- RESULTS: ${{ join(needs.*.result, ' ') }}
243
- run: |
244
- set -euo pipefail
245
- conclusion=success
246
- for result in $RESULTS; do
247
- case "$result" in
248
- failure) conclusion=failure; break ;;
249
- cancelled | timed_out) conclusion=cancelled ;;
250
- esac
251
- done
252
- # A cancelled run was superseded by a newer push, whose own run dispatches.
253
- if [ "$conclusion" = cancelled ]; then
254
- echo "CI was cancelled; not dispatching the merge gate."
255
- exit 0
256
- fi
257
- echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
258
- gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
259
- -f operation=merge-gate -f pr-number="$PR_NUMBER" \
260
- -f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
2
+ name: "App: CI (Node monorepo)"
3
+
4
+ on:
5
+ pull_request:
6
+ schedule:
7
+ - cron: "0 6 * * 1"
8
+ workflow_dispatch:
9
+
10
+ env:
11
+ NODE_VERSION: "22"
12
+ PNPM_VERSION: "10"
13
+ WEB_PACKAGE: "@app/web"
14
+ DESKTOP_PACKAGE: "@app/desktop"
15
+ MOBILE_PACKAGE: "@app/mobile"
16
+ WEB_EXPORT_PATH: apps/web/out
17
+ PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
18
+ DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
19
+ DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
20
+ DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
21
+ MOBILE_IOS_PATH: apps/mobile/ios
22
+ MOBILE_ANDROID_PATH: apps/mobile/android
23
+ WEB_ARTIFACT_NAME: web-export
24
+ PLAYWRIGHT_ARTIFACT_NAME: playwright-report
25
+ TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
26
+ SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
27
+ SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
28
+ MUTATION_THRESHOLD: "60"
29
+
30
+ permissions:
31
+ contents: read
32
+
33
+ concurrency:
34
+ group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
35
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
36
+
37
+ jobs:
38
+ lint:
39
+ name: Lint (Biome)
40
+ if: github.event_name != 'schedule'
41
+ runs-on: RunnerLandingZone
42
+ steps:
43
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
45
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
46
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
47
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
48
+ - run: pnpm install --frozen-lockfile
49
+ - run: pnpm exec biome check .
50
+
51
+ web:
52
+ name: Web (Next.js static export)
53
+ if: github.event_name != 'schedule'
54
+ runs-on: RunnerLandingZone
55
+ steps:
56
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
58
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
59
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
60
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
61
+ - run: pnpm install --frozen-lockfile
62
+ - run: pnpm --filter "$WEB_PACKAGE" run typecheck
63
+ - run: pnpm --filter "$WEB_PACKAGE" run test
64
+ - run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
65
+ - run: pnpm --filter "$WEB_PACKAGE" run build
66
+ - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
67
+ with:
68
+ name: ${{ env.WEB_ARTIFACT_NAME }}
69
+ path: ${{ env.WEB_EXPORT_PATH }}
70
+ retention-days: 3
71
+ if-no-files-found: error
72
+
73
+ mutation-web:
74
+ name: Mutation (web, diff)
75
+ if: github.event_name == 'pull_request'
76
+ runs-on: RunnerLandingZone
77
+ timeout-minutes: 25
78
+ steps:
79
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
80
+ with:
81
+ fetch-depth: 0
82
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
83
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
84
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
85
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
86
+ - name: Detect web changes
87
+ id: scope
88
+ run: |
89
+ git fetch origin "$GITHUB_BASE_REF" --depth=1
90
+ if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
91
+ echo "run=true" >> "$GITHUB_OUTPUT"
92
+ else
93
+ echo "run=false" >> "$GITHUB_OUTPUT"
94
+ echo "No apps/web changes in this PR; skipping web mutation."
95
+ fi
96
+ - run: pnpm install --frozen-lockfile
97
+ if: steps.scope.outputs.run == 'true'
98
+ - name: StrykerJS (changed files only)
99
+ if: steps.scope.outputs.run == 'true'
100
+ run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
101
+
102
+ e2e:
103
+ name: E2E (Playwright)
104
+ if: github.event_name != 'schedule'
105
+ needs: web
106
+ runs-on: RunnerLandingZone
107
+ steps:
108
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
109
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
110
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
111
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
112
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
113
+ - run: pnpm install --frozen-lockfile
114
+ - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
115
+ with:
116
+ name: ${{ env.WEB_ARTIFACT_NAME }}
117
+ path: ${{ env.WEB_EXPORT_PATH }}
118
+ - run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
119
+ - run: pnpm --filter "$WEB_PACKAGE" run e2e
120
+ - if: failure()
121
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
122
+ with:
123
+ name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
124
+ path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
125
+ retention-days: 7
126
+
127
+ desktop:
128
+ name: Desktop (Electron)
129
+ if: github.event_name != 'schedule'
130
+ needs: web
131
+ runs-on: RunnerLandingZone
132
+ steps:
133
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
134
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
135
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
136
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
137
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
138
+ - run: pnpm install --frozen-lockfile
139
+ - run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
140
+ - run: pnpm --filter "$DESKTOP_PACKAGE" run build
141
+ - run: |
142
+ test -f "$DESKTOP_MAIN_PATH"
143
+ test -f "$DESKTOP_PRELOAD_PATH"
144
+ grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
145
+ - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
146
+ with:
147
+ name: ${{ env.WEB_ARTIFACT_NAME }}
148
+ path: ${{ env.WEB_EXPORT_PATH }}
149
+ - run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
150
+ - run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
151
+
152
+ mobile:
153
+ name: Mobile (Capacitor)
154
+ if: github.event_name != 'schedule'
155
+ runs-on: RunnerLandingZone
156
+ steps:
157
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
158
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
159
+ with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
160
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
161
+ with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
162
+ - run: pnpm install --frozen-lockfile
163
+ - run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
164
+ - run: |
165
+ if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
166
+ pnpm --filter "$WEB_PACKAGE" run build
167
+ pnpm --filter "$MOBILE_PACKAGE" run sync
168
+ else
169
+ echo "::notice::Native projects absent; skipping Capacitor sync."
170
+ fi
171
+
172
+ secret-scan:
173
+ name: Secret scan (TruffleHog)
174
+ runs-on: RunnerLandingZone
175
+ steps:
176
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
177
+ - run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
178
+
179
+ deps-and-iac-scan:
180
+ name: Dependencies and IaC (Trivy)
181
+ runs-on: RunnerLandingZone
182
+ steps:
183
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
184
+ - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
185
+ with:
186
+ scan-type: fs
187
+ scanners: vuln,misconfig,secret
188
+ severity: CRITICAL,HIGH
189
+ ignore-unfixed: true
190
+ exit-code: "1"
191
+ format: table
192
+
193
+ sast-scan:
194
+ name: SAST (Semgrep)
195
+ runs-on: RunnerLandingZone
196
+ steps:
197
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
198
+ - run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
199
+
200
+ sbom:
201
+ name: SBOM (CycloneDX)
202
+ runs-on: RunnerLandingZone
203
+ steps:
204
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
205
+ - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
206
+ with:
207
+ format: cyclonedx-json
208
+ artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
209
+
210
+ # A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
211
+ # for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
212
+ # never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
213
+ # one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
214
+ # router treats a dispatch as trusted. Human pull requests are left to the workflow_run
215
+ # route, which does fire for them. The reconcile belt remains the hourly fallback.
216
+ #
217
+ # If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
218
+ # `actions: write`. A called workflow may not request a scope its caller withheld, and the
219
+ # whole run fails to start with zero jobs and no annotation when it does.
220
+ dispatch-merge-gate:
221
+ name: Dispatch merge gate
222
+ needs: [lint, web, mutation-web, e2e, desktop, mobile]
223
+ if: >
224
+ always() &&
225
+ github.event_name == 'pull_request' &&
226
+ github.event.pull_request.head.repo.full_name == github.repository &&
227
+ github.event.pull_request.draft == false &&
228
+ github.event.pull_request.user.type == 'Bot'
229
+ runs-on: RunnerLandingZone
230
+ timeout-minutes: 5
231
+ permissions:
232
+ contents: read
233
+ actions: write
234
+ steps:
235
+ - name: Dispatch the merge gate with this run's verdict
236
+ env:
237
+ GH_TOKEN: ${{ github.token }}
238
+ REPO: ${{ github.repository }}
239
+ REF: ${{ github.event.repository.default_branch }}
240
+ PR_NUMBER: ${{ github.event.pull_request.number }}
241
+ RUN_ID: ${{ github.run_id }}
242
+ RESULTS: ${{ join(needs.*.result, ' ') }}
243
+ run: |
244
+ set -euo pipefail
245
+ conclusion=success
246
+ for result in $RESULTS; do
247
+ case "$result" in
248
+ failure) conclusion=failure; break ;;
249
+ cancelled | timed_out) conclusion=cancelled ;;
250
+ esac
251
+ done
252
+ # A cancelled run was superseded by a newer push, whose own run dispatches.
253
+ if [ "$conclusion" = cancelled ]; then
254
+ echo "CI was cancelled; not dispatching the merge gate."
255
+ exit 0
256
+ fi
257
+ echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
258
+ gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
259
+ -f operation=merge-gate -f pr-number="$PR_NUMBER" \
260
+ -f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
@@ -43,11 +43,33 @@
43
43
  "models": {
44
44
  "glm-5-2": {
45
45
  "name": "GLM 5.3",
46
- "attachment": true
46
+ "tool_call": true,
47
+ "reasoning": true,
48
+ "attachment": true,
49
+ "modalities": {
50
+ "input": ["text", "image"],
51
+ "output": ["text"]
52
+ },
53
+ "limit": {
54
+ "context": 1048576,
55
+ "input": 1048576,
56
+ "output": 131072
57
+ }
47
58
  },
48
59
  "quasar-438b": {
49
60
  "name": "GLM 5.2",
50
- "attachment": true
61
+ "tool_call": true,
62
+ "reasoning": true,
63
+ "attachment": true,
64
+ "modalities": {
65
+ "input": ["text", "image"],
66
+ "output": ["text"]
67
+ },
68
+ "limit": {
69
+ "context": 1048576,
70
+ "input": 1048576,
71
+ "output": 131072
72
+ }
51
73
  }
52
74
  }
53
75
  }
@@ -81,6 +81,7 @@ jobs:
81
81
  # and gate both the reservation and the agent on it.
82
82
  still_open:
83
83
  runs-on: agents-arc
84
+ timeout-minutes: 5
84
85
  permissions:
85
86
  contents: read
86
87
  issues: read
@@ -99,6 +100,7 @@ jobs:
99
100
  issue-number: ${{ inputs.issue-number }}
100
101
  subject:
101
102
  runs-on: agents-arc
103
+ timeout-minutes: 5
102
104
  permissions:
103
105
  contents: read
104
106
  issues: read
@@ -168,6 +170,7 @@ jobs:
168
170
  needs: [subject, still_open]
169
171
  if: needs.subject.outputs.found == 'true' && needs.subject.outputs.issue != '' && needs.still_open.outputs.open == 'true'
170
172
  runs-on: agents-arc
173
+ timeout-minutes: 5
171
174
  permissions:
172
175
  contents: read
173
176
  issues: write
@@ -110,6 +110,7 @@ jobs:
110
110
  # and gate both the reservation and the agent on it.
111
111
  still_open:
112
112
  runs-on: agents-arc
113
+ timeout-minutes: 5
113
114
  permissions:
114
115
  contents: read
115
116
  issues: read
@@ -128,6 +129,7 @@ jobs:
128
129
  issue-number: ${{ inputs.issue-number }}
129
130
  eligibility:
130
131
  runs-on: agents-arc
132
+ timeout-minutes: 5
131
133
  permissions:
132
134
  issues: read
133
135
  outputs:
@@ -175,6 +177,7 @@ jobs:
175
177
  needs: [eligibility, still_open]
176
178
  if: needs.eligibility.outputs.eligible == 'true' && needs.still_open.outputs.open == 'true'
177
179
  runs-on: agents-arc
180
+ timeout-minutes: 5
178
181
  permissions:
179
182
  contents: read
180
183
  issues: write
@@ -141,6 +141,7 @@ on:
141
141
  jobs:
142
142
  subject:
143
143
  runs-on: agents-arc
144
+ timeout-minutes: 5
144
145
  permissions:
145
146
  contents: read
146
147
  issues: read
@@ -196,6 +197,7 @@ jobs:
196
197
  needs: subject
197
198
  if: needs.subject.outputs.found == 'true'
198
199
  runs-on: agents-arc
200
+ timeout-minutes: 5
199
201
  permissions:
200
202
  contents: read
201
203
  pull-requests: read
@@ -242,6 +244,7 @@ jobs:
242
244
  # there are no protected files instead of skipping it and blocking remediation.
243
245
  if: always() && needs.subject.outputs.found == 'true'
244
246
  runs-on: agents-arc
247
+ timeout-minutes: 5
245
248
  permissions:
246
249
  contents: read
247
250
  issues: write
@@ -301,6 +304,7 @@ jobs:
301
304
  needs: subject
302
305
  if: needs.subject.outputs.found == 'true'
303
306
  runs-on: agents-arc
307
+ timeout-minutes: 5
304
308
  permissions:
305
309
  contents: read
306
310
  issues: write