code-foundry 1.48.8 → 1.50.1

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.
@@ -38,6 +38,11 @@ on:
38
38
  required: false
39
39
  type: string
40
40
  default: task-result
41
+ integration-total-shards:
42
+ description: Integration partitions (1 or 2); sharded scripts must honor INTEGRATION_SHARD_INDEX and INTEGRATION_TOTAL_SHARDS.
43
+ required: false
44
+ type: string
45
+ default: '1'
41
46
  e2e-shard-list:
42
47
  description: Comma-separated E2E shard indices, for example "1,2". A single index runs one unsharded job.
43
48
  required: false
@@ -255,10 +260,14 @@ jobs:
255
260
 
256
261
  integration:
257
262
  name: Integration
258
- if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true
263
+ if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true && inputs.integration-total-shards != '2'
259
264
  runs-on: ${{ inputs.runner }}
260
265
  timeout-minutes: 30
261
266
  steps:
267
+ - name: Validate integration shard count
268
+ env:
269
+ SHARDS: ${{ inputs.integration-total-shards }}
270
+ run: test "$SHARDS" = 1
262
271
  - name: Checkout
263
272
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
264
273
  with:
@@ -326,6 +335,86 @@ jobs:
326
335
  - name: Save Vite+ task cache
327
336
  uses: ./.github/actions/vite-plus-cache-save
328
337
 
338
+ integration-sharded:
339
+ name: Integration shard
340
+ if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true && inputs.integration-total-shards == '2'
341
+ runs-on: ${{ inputs.runner }}
342
+ timeout-minutes: 30
343
+ strategy:
344
+ fail-fast: false
345
+ matrix:
346
+ shard: [1, 2]
347
+ steps:
348
+ - name: Checkout
349
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
350
+ with:
351
+ persist-credentials: false
352
+ fetch-depth: 2
353
+ - name: Runtime
354
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
355
+ with:
356
+ persist-credentials: false
357
+ repository: ${{ inputs.runtime-repository }}
358
+ ref: ${{ inputs.runtime-ref }}
359
+ path: .github/.code-foundry
360
+ sparse-checkout: |
361
+ .github/actions
362
+ src/lib
363
+ src/runtime-core.mjs
364
+ src/runtime.mjs
365
+ - name: Install runtime
366
+ run: |
367
+ mkdir -p .github/actions
368
+ mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
369
+ cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
370
+ - name: Detect
371
+ id: applicability
372
+ env:
373
+ EVENT_NAME: ${{ github.event_name }}
374
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
375
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
376
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
377
+ BEFORE_SHA: ${{ github.event.before }}
378
+ run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile integration >> "$GITHUB_OUTPUT"
379
+ - name: Setup
380
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
381
+ uses: ./.github/actions/setup
382
+ with:
383
+ install: true
384
+ install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
385
+ install-python: ${{ steps.applicability.outputs.python == 'true' }}
386
+ install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
387
+ cache-build: ${{ steps.applicability.outputs.rust == 'true' }}
388
+ cache-environment: false
389
+ cache-save: ${{ github.event_name == 'push' }}
390
+ task: integration
391
+ task-javascript: ${{ steps.applicability.outputs.javascript }}
392
+ task-python: ${{ steps.applicability.outputs.python }}
393
+ task-rust: ${{ steps.applicability.outputs.rust }}
394
+ - name: Integration
395
+ id: execute
396
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
397
+ env:
398
+ INTEGRATION_SHARD_INDEX: ${{ matrix.shard }}
399
+ INTEGRATION_TOTAL_SHARDS: ${{ inputs.integration-total-shards }}
400
+ run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci integration
401
+ - name: Retain task receipt
402
+ if: >-
403
+ always() &&
404
+ (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
405
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
406
+ hashFiles('.code-foundry/results/integration.json') != ''
407
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
408
+ with:
409
+ name: ${{ inputs.artifact-prefix }}-${{ github.run_id }}-${{ github.run_attempt }}-integration-s${{ matrix.shard }}
410
+ path: .code-foundry/results/integration.json
411
+ include-hidden-files: true
412
+ if-no-files-found: error
413
+ retention-days: 14
414
+
415
+ - name: Save Vite+ task cache
416
+ uses: ./.github/actions/vite-plus-cache-save
417
+
329
418
  e2e:
330
419
  name: E2E
331
420
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true
@@ -25,6 +25,11 @@ on:
25
25
  description: Validation tier (fast, audit, or release).
26
26
  required: true
27
27
  type: string
28
+ release-qualification-receipts:
29
+ description: Reuse a recent exact-tree audit receipt for Release Please PRs.
30
+ required: false
31
+ type: boolean
32
+ default: false
28
33
  codeql:
29
34
  description: Run the CodeQL lane (the consumer's canonical codeql config).
30
35
  required: false
@@ -95,6 +100,11 @@ on:
95
100
  required: false
96
101
  type: string
97
102
  default: task-result
103
+ integration-total-shards:
104
+ description: Integration partitions (1 or 2); sharded scripts must honor INTEGRATION_SHARD_INDEX and INTEGRATION_TOTAL_SHARDS.
105
+ required: false
106
+ type: string
107
+ default: '1'
98
108
  e2e-shard-list:
99
109
  description: Comma-separated E2E shard indices passed to the test lane's matrix, e.g. "1,2".
100
110
  required: false
@@ -118,6 +128,7 @@ permissions:
118
128
  actions: read
119
129
  contents: read
120
130
  packages: read
131
+ pull-requests: read
121
132
  security-events: write
122
133
 
123
134
  env:
@@ -141,16 +152,41 @@ concurrency:
141
152
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
142
153
 
143
154
  jobs:
144
- # Merged low-cost lanes. Each block below is a step-for-step mirror of the
145
- # corresponding lane job in ci.yml, test.yml, or eval.yml; only the step ids
146
- # are prefixed and subsequent lanes add !cancelled() so one lane's failure
147
- # never hides another lane's result.
155
+ qualification:
156
+ name: Qualification
157
+ if: >-
158
+ vars.CI_BILLING_PAUSED != 'true' &&
159
+ inputs.release-qualification-receipts == true && inputs.mode == 'release'
160
+ permissions:
161
+ actions: read
162
+ contents: read
163
+ pull-requests: read
164
+ uses: ./.github/workflows/qualification-receipt.yml
165
+ with:
166
+ phase: verify
167
+ mode: ${{ inputs.mode }}
168
+ enabled: ${{ inputs.release-qualification-receipts }}
169
+ codeql-enabled: ${{ inputs.codeql }}
170
+ runtime-repository: ${{ inputs.runtime-repository }}
171
+ runtime-ref: ${{ inputs.runtime-ref }}
172
+ required-job-names-json: '["Validation / Gate"]'
173
+
148
174
  fast-lanes:
149
175
  name: Fast lanes
150
- if: vars.CI_BILLING_PAUSED != 'true'
176
+ needs: qualification
177
+ if: >-
178
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true'
179
+ permissions:
180
+ actions: read
181
+ contents: read
182
+ packages: read
151
183
  runs-on: ${{ inputs.ci-runner }}
152
184
  timeout-minutes: 45
153
185
  steps:
186
+ - name: Validate integration shard count
187
+ env:
188
+ SHARDS: ${{ inputs.integration-total-shards }}
189
+ run: test "$SHARDS" = 1
154
190
  - name: Checkout
155
191
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
156
192
  with:
@@ -165,6 +201,7 @@ jobs:
165
201
  path: .github/.code-foundry
166
202
  sparse-checkout: |
167
203
  .github/actions
204
+ src/commands/qualification-receipt.mjs
168
205
  src/lib
169
206
  src/runtime-core.mjs
170
207
  src/runtime.mjs
@@ -173,9 +210,15 @@ jobs:
173
210
  mkdir -p .github/actions
174
211
  mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
175
212
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
213
+ - name: Set up Node.js 24.18.0 for package inspection
214
+ if: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
215
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
216
+ with:
217
+ node-version: 24.18.0
176
218
  # --- Format lane (mirror of ci.yml format) ---
177
219
  - name: Detect format
178
220
  id: format_applicability
221
+ if: ${{ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true') }}
179
222
  env:
180
223
  EVENT_NAME: ${{ github.event_name }}
181
224
  BASE_REF: ${{ github.event.pull_request.base.ref }}
@@ -194,6 +237,11 @@ jobs:
194
237
  HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
195
238
  BEFORE_SHA: ${{ github.event.before }}
196
239
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run build >> "$GITHUB_OUTPUT"
240
+ - name: Verify versioned package build contract
241
+ if: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
242
+ env:
243
+ FOUNDRY_BUILD_TASK_APPLICABLE: ${{ steps.build_applicability.outputs.applicable }}
244
+ run: node "$RUNNER_TEMP/code-foundry/src/commands/qualification-receipt.mjs" package-contract
197
245
  # --- Performance lane (mirror of test.yml performance; audit tier only) ---
198
246
  - name: Detect performance
199
247
  id: performance_applicability
@@ -239,7 +287,10 @@ jobs:
239
287
  BEFORE_SHA: ${{ github.event.before }}
240
288
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile integration >> "$GITHUB_OUTPUT"
241
289
  - name: Setup format
242
- if: steps.format_applicability.outputs.applicable == 'true' && steps.format_applicability.outputs.affected != 'false'
290
+ if: >-
291
+ ${{ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true') &&
292
+ steps.format_applicability.outputs.applicable == 'true' &&
293
+ steps.format_applicability.outputs.affected != 'false' }}
243
294
  uses: ./.github/actions/setup
244
295
  with:
245
296
  # Repository format scripts normally call a project-local formatter
@@ -251,7 +302,10 @@ jobs:
251
302
  cache-build: 'false'
252
303
  task: format
253
304
  - name: Setup build
254
- if: ${{ !cancelled() && steps.build_applicability.outputs.applicable == 'true' && steps.build_applicability.outputs.affected != 'false' }}
305
+ if: >-
306
+ ${{ !cancelled() && steps.build_applicability.outputs.applicable == 'true' &&
307
+ ((inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true') ||
308
+ steps.build_applicability.outputs.affected != 'false') }}
255
309
  uses: ./.github/actions/setup
256
310
  with:
257
311
  install: 'true'
@@ -323,12 +377,18 @@ jobs:
323
377
  # --- Format execute ---
324
378
  - name: Format
325
379
  id: format_execute
326
- if: steps.format_applicability.outputs.applicable == 'true' && steps.format_applicability.outputs.affected != 'false'
380
+ if: >-
381
+ ${{ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true') &&
382
+ steps.format_applicability.outputs.applicable == 'true' &&
383
+ steps.format_applicability.outputs.affected != 'false' }}
327
384
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci format
328
385
  # --- Build execute ---
329
386
  - name: Build
330
387
  id: build_execute
331
- if: ${{ !cancelled() && steps.build_applicability.outputs.applicable == 'true' && steps.build_applicability.outputs.affected != 'false' }}
388
+ if: >-
389
+ ${{ !cancelled() && steps.build_applicability.outputs.applicable == 'true' &&
390
+ ((inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true') ||
391
+ steps.build_applicability.outputs.affected != 'false') }}
332
392
  env:
333
393
  NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET || 'code-foundry-ci-placeholder-0123456789' }}
334
394
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci build
@@ -352,6 +412,21 @@ jobs:
352
412
  id: integration_execute
353
413
  if: ${{ !cancelled() && inputs.mode == 'audit' && steps.integration_applicability.outputs.applicable == 'true' && steps.integration_applicability.outputs.affected != 'false' }}
354
414
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci integration
415
+ - name: Inspect versioned npm package contents
416
+ if: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' && hashFiles('package.json') != '' }}
417
+ run: |
418
+ npm pack --dry-run --ignore-scripts --json > "$RUNNER_TEMP/foundry-package-inspection.json"
419
+ node --input-type=module -e '
420
+ import { readFileSync } from "node:fs";
421
+ import { join } from "node:path";
422
+ import { pathToFileURL } from "node:url";
423
+
424
+ const runtime = join(process.env.RUNNER_TEMP, "code-foundry", "src/lib/node-package-performance.mjs");
425
+ const { verifyNpmPackPackage } = await import(pathToFileURL(runtime).href);
426
+ const manifest = JSON.parse(readFileSync("package.json", "utf8"));
427
+ const report = JSON.parse(readFileSync(process.argv[1], "utf8"));
428
+ verifyNpmPackPackage(report, manifest);
429
+ ' "$RUNNER_TEMP/foundry-package-inspection.json"
355
430
  # Upload only after execution joins, and keep uploads sequential: the
356
431
  # runner rewrites GITHUB_EVENT_PATH for each action, racing the event
357
432
  # JSON read in upload-artifact's context initialization (runner#4641).
@@ -473,7 +548,14 @@ jobs:
473
548
 
474
549
  lint:
475
550
  name: Lint
476
- if: vars.CI_BILLING_PAUSED != 'true'
551
+ needs: qualification
552
+ if: >-
553
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
554
+ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true')
555
+ permissions:
556
+ actions: read
557
+ contents: read
558
+ packages: read
477
559
  runs-on: ${{ inputs.ci-runner }}
478
560
  timeout-minutes: 30
479
561
  steps:
@@ -537,7 +619,10 @@ jobs:
537
619
 
538
620
  type-check:
539
621
  name: Type-Check
540
- if: vars.CI_BILLING_PAUSED != 'true'
622
+ needs: qualification
623
+ if: >-
624
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
625
+ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true')
541
626
  permissions:
542
627
  contents: read
543
628
  issues: read
@@ -606,7 +691,14 @@ jobs:
606
691
 
607
692
  unit:
608
693
  name: Unit
609
- if: vars.CI_BILLING_PAUSED != 'true'
694
+ needs: qualification
695
+ if: >-
696
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
697
+ !(inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true')
698
+ permissions:
699
+ actions: read
700
+ contents: read
701
+ packages: read
610
702
  runs-on: ${{ inputs.unit-runner }}
611
703
  timeout-minutes: 30
612
704
  steps:
@@ -696,6 +788,10 @@ jobs:
696
788
  e2e:
697
789
  name: E2E
698
790
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
791
+ permissions:
792
+ actions: read
793
+ contents: read
794
+ packages: read
699
795
  runs-on: ${{ inputs.test-runner }}
700
796
  timeout-minutes: 45
701
797
  # Shards duplicate the whole job, but each shard owns a runner-local test
@@ -798,6 +894,10 @@ jobs:
798
894
  audit:
799
895
  name: Audit
800
896
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
897
+ permissions:
898
+ actions: read
899
+ contents: read
900
+ packages: read
801
901
  runs-on: ${{ inputs.security-runner }}
802
902
  timeout-minutes: 30
803
903
  steps:
@@ -1103,6 +1203,11 @@ jobs:
1103
1203
  # Release-please pull requests also analyze with CodeQL so repository
1104
1204
  # rulesets requiring code scanning results never deadlock the release.
1105
1205
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.codeql == true && (inputs.mode == 'audit' || inputs.mode == 'release')
1206
+ permissions:
1207
+ actions: read
1208
+ contents: read
1209
+ packages: read
1210
+ security-events: write
1106
1211
  uses: ./.github/workflows/codeql.yml
1107
1212
  with:
1108
1213
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -1115,24 +1220,25 @@ jobs:
1115
1220
  # Aggregate check: "Validation / Gate" (caller job name + this job name).
1116
1221
  # Always evaluates; only jobs required for the mode must have succeeded.
1117
1222
  # Category results fold the merged job's lanes back into the same FOUNDRY_*
1118
- # vocabulary the split orchestrators feed the runtime gate, so
1119
- # evaluateValidationGate's mode truth table is reused unchanged.
1223
+ # vocabulary the split orchestrators feed the runtime gate. Exact source
1224
+ # receipts can replace repeat lint/type/unit work; Build, package inspection,
1225
+ # CodeQL when configured, and the receipt gate remain required.
1120
1226
  gate:
1121
1227
  name: Gate
1122
- needs: [fast-lanes, lint, type-check, unit, e2e, audit, codeql]
1228
+ needs: [fast-lanes, lint, type-check, unit, e2e, audit, codeql, qualification]
1123
1229
  if: vars.CI_BILLING_PAUSED != 'true' && always()
1230
+ permissions:
1231
+ contents: read
1124
1232
  runs-on: ubuntu-slim
1125
1233
  timeout-minutes: 10
1126
1234
  env:
1127
1235
  FOUNDRY_MODE: ${{ inputs.mode }}
1128
- # CI category: lint and type-check always run; the merged fast-lanes job
1129
- # carries the format and build lanes.
1130
- FOUNDRY_CI: ${{ (needs.lint.result != 'success' && needs.lint.result) || (needs.type-check.result != 'success' && needs.type-check.result) || (needs.fast-lanes.result != 'success' && needs.fast-lanes.result) || 'success' }}
1131
- # Test category: unit always runs and must succeed; integration and e2e
1132
- # are audit-tier jobs whose expected skip on fast/release is not a
1133
- # failure; the merged fast-lanes job carries performance, smoke, and
1134
- # eval-mode lanes, so its result joins the category too.
1135
- FOUNDRY_TEST: ${{ needs.unit.result == 'success' && needs.fast-lanes.result == 'success' && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && 'success' || ((needs.e2e.result != 'success' && needs.e2e.result != 'skipped' && needs.e2e.result) || (needs.unit.result != 'success' && needs.unit.result) || (needs.fast-lanes.result != 'success' && needs.fast-lanes.result) || 'failure') }}
1236
+ # CI category: normal and fallback runs include lint/type-check. A
1237
+ # verified source receipt retains the build/package lane here.
1238
+ FOUNDRY_CI: ${{ (inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' && needs.fast-lanes.result == 'success' && 'success') || ((needs.lint.result != 'success' && needs.lint.result) || (needs.type-check.result != 'success' && needs.type-check.result) || (needs.fast-lanes.result != 'success' && needs.fast-lanes.result) || 'success') }}
1239
+ # Test category: a verified receipt replaces duplicate unit-test work;
1240
+ # otherwise unit plus applicable integration/E2E lanes must succeed.
1241
+ FOUNDRY_TEST: ${{ (inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' && needs.fast-lanes.result == 'success' && 'success') || (needs.unit.result == 'success' && needs.fast-lanes.result == 'success' && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && 'success') || ((needs.e2e.result != 'success' && needs.e2e.result != 'skipped' && needs.e2e.result) || (needs.unit.result != 'success' && needs.unit.result) || (needs.fast-lanes.result != 'success' && needs.fast-lanes.result) || 'failure') }}
1136
1242
  FOUNDRY_SECURITY: ${{ needs.audit.result }}
1137
1243
  # This orchestrator runs CodeQL only when the consumer's canonical
1138
1244
  # configuration enables it; otherwise the unavailability decision is
@@ -1142,6 +1248,8 @@ jobs:
1142
1248
  # fast-lanes failure on that tier marks the eval category failed even if
1143
1249
  # another merged lane caused it, which fails closed.
1144
1250
  FOUNDRY_EVAL: ${{ needs.fast-lanes.result }}
1251
+ FOUNDRY_SOURCE_QUALIFICATION: ${{ needs.qualification.outputs.reuse }}
1252
+ FOUNDRY_QUALIFICATION: ${{ needs.qualification.result }}
1145
1253
  steps:
1146
1254
  - name: Checkout runtime
1147
1255
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -1173,3 +1281,31 @@ jobs:
1173
1281
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation release_diff
1174
1282
  - name: Evaluate aggregate gate
1175
1283
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation gate
1284
+
1285
+ qualification-receipt:
1286
+ name: Qualification receipt
1287
+ needs: [fast-lanes, lint, type-check, unit, e2e, audit, codeql, gate]
1288
+ if: >-
1289
+ vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit' &&
1290
+ inputs.release-qualification-receipts == true &&
1291
+ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' &&
1292
+ needs.fast-lanes.result == 'success' && needs.lint.result == 'success' &&
1293
+ needs.type-check.result == 'success' && needs.unit.result == 'success' &&
1294
+ needs.e2e.result == 'success' && needs.audit.result == 'success' &&
1295
+ (inputs.codeql != true || needs.codeql.result == 'success') &&
1296
+ needs.gate.result == 'success'
1297
+ permissions:
1298
+ actions: read
1299
+ contents: read
1300
+ pull-requests: read
1301
+ uses: ./.github/workflows/qualification-receipt.yml
1302
+ with:
1303
+ phase: produce
1304
+ mode: ${{ inputs.mode }}
1305
+ enabled: ${{ inputs.release-qualification-receipts }}
1306
+ codeql-enabled: ${{ inputs.codeql }}
1307
+ runtime-repository: ${{ inputs.runtime-repository }}
1308
+ runtime-ref: ${{ inputs.runtime-ref }}
1309
+ job-results-json: >-
1310
+ {"ci":"${{ needs.lint.result == 'success' && needs.type-check.result == 'success' && needs.fast-lanes.result == 'success' && 'success' || 'failure' }}","test":"${{ needs.unit.result == 'success' && needs.fast-lanes.result == 'success' && needs.e2e.result == 'success' && 'success' || 'failure' }}","security":"${{ needs.audit.result }}","codeql":"${{ inputs.codeql == true && needs.codeql.result || 'not-configured' }}","eval":"${{ needs.fast-lanes.result }}","gate":"${{ needs.gate.result }}"}
1311
+ required-job-names-json: '["Validation / Gate"]'
@@ -7,6 +7,11 @@ on:
7
7
  description: Validation tier (fast, audit, or release).
8
8
  required: true
9
9
  type: string
10
+ release-qualification-receipts:
11
+ description: Reuse a recent exact-tree audit receipt for Release Please PRs.
12
+ required: false
13
+ type: boolean
14
+ default: false
10
15
  runtime-repository:
11
16
  description: Repository containing the Code Foundry runtime.
12
17
  required: false
@@ -75,6 +80,11 @@ on:
75
80
  required: false
76
81
  type: string
77
82
  default: task-result
83
+ integration-total-shards:
84
+ description: Integration partitions (1 or 2); sharded scripts must honor INTEGRATION_SHARD_INDEX and INTEGRATION_TOTAL_SHARDS.
85
+ required: false
86
+ type: string
87
+ default: '1'
78
88
  e2e-shard-list:
79
89
  description: Comma-separated E2E shard indices passed to the test lane's matrix, e.g. "1,2".
80
90
  required: false
@@ -98,12 +108,35 @@ permissions:
98
108
  actions: read
99
109
  contents: read
100
110
  packages: read
111
+ pull-requests: read
101
112
  security-events: write
102
113
 
103
114
  jobs:
115
+ qualification:
116
+ name: Qualification
117
+ if: >-
118
+ vars.CI_BILLING_PAUSED != 'true' &&
119
+ inputs.release-qualification-receipts == true && inputs.mode == 'release'
120
+ permissions:
121
+ actions: read
122
+ contents: read
123
+ pull-requests: read
124
+ uses: ./.github/workflows/qualification-receipt.yml
125
+ with:
126
+ phase: verify
127
+ mode: ${{ inputs.mode }}
128
+ enabled: ${{ inputs.release-qualification-receipts }}
129
+ codeql-enabled: false
130
+ runtime-repository: ${{ inputs.runtime-repository }}
131
+ runtime-ref: ${{ inputs.runtime-ref }}
132
+ required-job-names-json: '["Validation / Gate"]'
133
+
104
134
  ci:
105
135
  name: CI
106
- if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
136
+ needs: qualification
137
+ if: >-
138
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
139
+ (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
107
140
  # Forward issue-read only to the CI workflow that runs Type-Check.
108
141
  permissions:
109
142
  actions: read
@@ -116,13 +149,23 @@ jobs:
116
149
  runtime-ref: ${{ inputs.runtime-ref }}
117
150
  runner: ${{ inputs.ci-runner }}
118
151
  artifact-prefix: ${{ inputs.artifact-prefix }}
152
+ release-build-only: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
153
+ release-package-check: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
119
154
  secrets:
120
155
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
121
156
  NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
122
157
 
123
158
  test:
124
159
  name: Test
125
- if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
160
+ needs: qualification
161
+ if: >-
162
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
163
+ (inputs.mode == 'fast' || inputs.mode == 'audit' ||
164
+ (inputs.mode == 'release' && needs.qualification.outputs.reuse != 'true'))
165
+ permissions:
166
+ actions: read
167
+ contents: read
168
+ packages: read
126
169
  uses: ./.github/workflows/test.yml
127
170
  with:
128
171
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -130,11 +173,12 @@ jobs:
130
173
  runner: ${{ inputs.test-runner }}
131
174
  unit-runner: ${{ inputs.unit-runner }}
132
175
  performance-runner: ${{ inputs.performance-runner }}
133
- # Release pull requests change version metadata only; their content was
134
- # audited on the pull requests that merged into main. Integration, E2E,
135
- # smoke, and performance lanes stay in the audit and scheduled lanes.
176
+ # Fast and Release Please pull requests run unit tests only. Until a
177
+ # successful full qualification is verifiable for the exact main tree,
178
+ # release mode keeps its CI and unit-test fallback.
136
179
  unit-only: ${{ inputs.mode == 'fast' || inputs.mode == 'release' }}
137
180
  artifact-prefix: ${{ inputs.artifact-prefix }}
181
+ integration-total-shards: ${{ inputs.integration-total-shards }}
138
182
  e2e-shard-list: ${{ inputs.e2e-shard-list }}
139
183
  e2e-total-shards: ${{ inputs.e2e-total-shards }}
140
184
  secrets:
@@ -143,6 +187,8 @@ jobs:
143
187
  security:
144
188
  name: Security
145
189
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
190
+ permissions:
191
+ contents: read
146
192
  uses: ./.github/workflows/security.yml
147
193
  with:
148
194
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -151,10 +197,12 @@ jobs:
151
197
 
152
198
  eval:
153
199
  name: Eval
154
- # Behavior evals cover content changes; the lean release lane skips them
155
- # because its diff is version metadata only. The reusable workflow skips
156
- # cleanly when the repository has no eval harness.
200
+ # Behavior evals are part of audit qualification. A release-only run may
201
+ # reuse that work only through the exact, unexpired source receipt below.
202
+ # The reusable workflow skips cleanly when there is no eval harness.
157
203
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
204
+ permissions:
205
+ contents: read
158
206
  uses: ./.github/workflows/eval.yml
159
207
  with:
160
208
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -164,16 +212,16 @@ jobs:
164
212
 
165
213
  # Aggregate check: "Validation / Gate" (caller job name + this job name).
166
214
  # Always evaluates; only jobs required for the mode must have succeeded.
167
- # The release tier requires the available fast suite; its diff is version
168
- # metadata (validated against the release policy below), its content was
169
- # audited on the pull requests that merged into main, and the scheduled
170
- # audit lane re-covers drift. Its generated release diff policy executes
171
- # as a conditional step below, so no separate release-policy job renders
172
- # skipped checks on ordinary pull requests.
215
+ # Release Please branches require CI and the exact generated-diff audit
216
+ # below. The runtime gate also requires unit tests unless the separate
217
+ # qualification job verified a matching source receipt. Ordinary PRs still
218
+ # render no skipped release-policy row.
173
219
  gate:
174
220
  name: Gate
175
- needs: [ci, test, security, eval]
221
+ needs: [ci, test, security, eval, qualification]
176
222
  if: vars.CI_BILLING_PAUSED != 'true' && always()
223
+ permissions:
224
+ contents: read
177
225
  runs-on: ubuntu-slim
178
226
  timeout-minutes: 10
179
227
  env:
@@ -182,6 +230,8 @@ jobs:
182
230
  FOUNDRY_TEST: ${{ needs.test.result }}
183
231
  FOUNDRY_SECURITY: ${{ needs.security.result }}
184
232
  FOUNDRY_EVAL: ${{ needs.eval.result }}
233
+ FOUNDRY_SOURCE_QUALIFICATION: ${{ needs.qualification.outputs.reuse }}
234
+ FOUNDRY_QUALIFICATION: ${{ needs.qualification.result }}
185
235
  # This workflow is selected only for repositories whose canonical
186
236
  # configuration explicitly disables unavailable CodeQL. Treat that
187
237
  # policy decision as satisfied without registering a skipped PR check.
@@ -217,3 +267,29 @@ jobs:
217
267
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation release_diff
218
268
  - name: Evaluate aggregate gate
219
269
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation gate
270
+
271
+ qualification-receipt:
272
+ name: Qualification receipt
273
+ needs: [ci, test, security, eval, gate]
274
+ if: >-
275
+ vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit' &&
276
+ inputs.release-qualification-receipts == true &&
277
+ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' &&
278
+ needs.ci.result == 'success' && needs.test.result == 'success' &&
279
+ needs.security.result == 'success' && needs.eval.result == 'success' &&
280
+ needs.gate.result == 'success'
281
+ permissions:
282
+ actions: read
283
+ contents: read
284
+ pull-requests: read
285
+ uses: ./.github/workflows/qualification-receipt.yml
286
+ with:
287
+ phase: produce
288
+ mode: ${{ inputs.mode }}
289
+ enabled: ${{ inputs.release-qualification-receipts }}
290
+ codeql-enabled: false
291
+ runtime-repository: ${{ inputs.runtime-repository }}
292
+ runtime-ref: ${{ inputs.runtime-ref }}
293
+ job-results-json: >-
294
+ {"ci":"${{ needs.ci.result }}","test":"${{ needs.test.result }}","security":"${{ needs.security.result }}","codeql":"not-configured","eval":"${{ needs.eval.result }}","gate":"${{ needs.gate.result }}"}
295
+ required-job-names-json: '["Validation / Gate"]'