44 push :
55 branches : [main]
66 pull_request :
7+ schedule :
8+ - cron : " 17 3 * * *"
79 workflow_dispatch :
10+ inputs :
11+ validation_scope :
12+ description : Validation scope
13+ required : true
14+ default : affected
15+ type : choice
16+ options :
17+ - affected
18+ - full
819
920permissions :
1021 contents : read
1122
1223jobs :
13- supported-control-plane :
14- name : Control plane (${{ matrix.os }}, Node ${{ matrix.node }})
24+ plan :
25+ name : Plan affected validation
26+ runs-on : ubuntu-latest
27+ outputs :
28+ run-affected : ${{ steps.plan.outputs.run-affected }}
29+ run-impact-validation : ${{ steps.plan.outputs.run-impact-validation }}
30+ run-full-quality : ${{ steps.plan.outputs.run-full-quality }}
31+ run-package-boundary : ${{ steps.plan.outputs.run-package-boundary }}
32+ matrix : ${{ steps.plan.outputs.matrix }}
33+ planner-digest : ${{ steps.plan.outputs.planner-digest }}
34+ changed-files-b64 : ${{ steps.plan.outputs.changed-files-b64 }}
35+ plan-b64 : ${{ steps.plan.outputs.plan-b64 }}
36+ steps :
37+ - uses : actions/checkout@v6
38+ with :
39+ fetch-depth : 0
40+ - name : Build ValidationImpactGraphV2 CI plan
41+ id : plan
42+ shell : bash
43+ env :
44+ BASE_SHA : ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
45+ MANUAL_SCOPE : ${{ inputs.validation_scope || 'affected' }}
46+ run : |
47+ set -euo pipefail
48+ node --check scripts/plan-ci-validation.js
49+ node scripts/plan-ci-validation.js \
50+ --event "${GITHUB_EVENT_NAME}" \
51+ --base "${BASE_SHA}" \
52+ --head "${GITHUB_SHA}" \
53+ --manual-scope "${MANUAL_SCOPE}" \
54+ --github-output "${GITHUB_OUTPUT}" \
55+ > "${RUNNER_TEMP}/ci-validation-plan.json"
56+ node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(`### CI validation plan\n\n- planner: \`${p.plannerDigest}\`\n- changed: ${p.totalChangedFiles}\n- affected: ${p.jobs.affected}\n- full: ${p.jobs.fullQuality}${p.fullReasonCodes.length?` (${p.fullReasonCodes.join(", ")})`:""}\n- package: ${p.jobs.packageBoundary}\n`)' "${RUNNER_TEMP}/ci-validation-plan.json" >> "${GITHUB_STEP_SUMMARY}"
57+
58+ affected :
59+ name : Affected (${{ matrix.id }}, Node ${{ matrix.node }})
60+ needs : plan
61+ if : needs.plan.outputs.run-affected == 'true'
1562 runs-on : ${{ matrix.os }}
1663 timeout-minutes : 30
1764 strategy :
1865 fail-fast : false
19- matrix :
20- include :
21- - os : ubuntu-latest
22- node : 18.17.0
23- route : test:supported-runtime-control-plane
24- - os : ubuntu-latest
25- node : 22.x
26- route : test:supported-runtime-control-plane
27- - os : ubuntu-latest
28- node : 26.x
29- route : test:supported-runtime-control-plane
30- - os : windows-latest
31- node : 18.17.0
32- route : test:windows-control-plane
33- - os : windows-latest
34- node : 24.17.0
35- route : test:windows-control-plane
66+ matrix : ${{ fromJSON(needs.plan.outputs.matrix) }}
3667 steps :
3768 - uses : actions/checkout@v6
3869 - uses : actions/setup-node@v6
3970 with :
4071 node-version : ${{ matrix.node }}
4172 cache : npm
4273 - run : npm ci
43- - run : npm run ${{ matrix.route }}
74+ - name : Run exact affected validation plan
75+ if : matrix.kind == 'impact'
76+ shell : bash
77+ env :
78+ CHANGED_FILES_B64 : ${{ needs.plan.outputs.changed-files-b64 }}
79+ CI_PLAN_B64 : ${{ needs.plan.outputs.plan-b64 }}
80+ run : |
81+ set -euo pipefail
82+ mapfile -d '' CHANGED_FILES < <(node -e 'for(const f of JSON.parse(Buffer.from(process.env.CHANGED_FILES_B64,"base64").toString("utf8"))) process.stdout.write(f+"\0")')
83+ CHANGED_ARGS=()
84+ for file in "${CHANGED_FILES[@]}"; do CHANGED_ARGS+=(--changed "${file}"); done
85+ AUTHORITY_SOURCE="github-actions:${GITHUB_WORKFLOW}:${GITHUB_EVENT_NAME}:${GITHUB_REPOSITORY}:${GITHUB_REF}:${GITHUB_SHA}"
86+ VALIDATION_LEVEL="$(node -e 'const p=JSON.parse(Buffer.from(process.env.CI_PLAN_B64,"base64").toString("utf8")); process.stdout.write(p.effective.verificationLevel)')"
87+ export VALIDATION_LEVEL
88+ POLICY_DIGEST="$(node -e 'const {sha256}=require("./hooks/_runtime/content-identity.cjs"); process.stdout.write(sha256(JSON.stringify({workflow:process.env.GITHUB_WORKFLOW,event:process.env.GITHUB_EVENT_NAME,repository:process.env.GITHUB_REPOSITORY,ref:process.env.GITHUB_REF,commit:process.env.GITHUB_SHA,level:process.env.VALIDATION_LEVEL})))' )"
89+ BUDGET_DIGEST="$(
90+ node scripts/run-validation.js --route changed "${CHANGED_ARGS[@]}" --actor trusted-ci --authority-source "${AUTHORITY_SOURCE}" --policy-digest "${POLICY_DIGEST}" --plan --json |
91+ node -e 'const fs=require("fs"); const value=JSON.parse(fs.readFileSync(0,"utf8")); const digest=value?.data?.plan?.budgetCard?.digest; if(value?.ok!==true||!/^[a-f0-9]{64}$/.test(String(digest||""))) process.exit(1); process.stdout.write(digest)'
92+ )"
93+ node scripts/run-validation.js --route changed "${CHANGED_ARGS[@]}" --actor trusted-ci --authority-source "${AUTHORITY_SOURCE}" --policy-digest "${POLICY_DIGEST}" --approve-plan "${BUDGET_DIGEST}" --json > "${RUNNER_TEMP}/ci-validation-result-${{ matrix.id }}.json"
94+ - name : Run scheduled compatibility route
95+ if : matrix.kind == 'compatibility'
96+ run : npm run ${{ matrix.command }}
97+ - name : Preserve affected validation receipt
98+ if : always() && matrix.kind == 'impact'
99+ uses : actions/upload-artifact@v4
100+ with :
101+ name : ci-validation-result-${{ matrix.id }}-${{ github.run_id }}-${{ github.run_attempt }}
102+ path : ${{ runner.temp }}/ci-validation-result-${{ matrix.id }}.json
103+ if-no-files-found : error
104+ retention-days : 14
44105
45106 full-quality :
46107 name : Full quality (Node 24.17)
108+ needs : plan
109+ if : needs.plan.outputs.run-full-quality == 'true'
47110 runs-on : ubuntu-latest
48111 steps :
49112 - uses : actions/checkout@v6
@@ -64,12 +127,22 @@ jobs:
64127 node scripts/run-validation.js --route full --actor trusted-ci --authority-source "${AUTHORITY_SOURCE}" --policy-digest "${POLICY_DIGEST}" --plan --json |
65128 node -e 'const fs=require("fs"); const value=JSON.parse(fs.readFileSync(0,"utf8")); const digest=value?.data?.plan?.budgetCard?.digest; if(value?.ok!==true || !/^[a-f0-9]{64}$/.test(String(digest||""))) process.exit(1); process.stdout.write(digest)'
66129 )"
67- node scripts/run-validation.js --route full --actor trusted-ci --authority-source "${AUTHORITY_SOURCE}" --policy-digest "${POLICY_DIGEST}" --approve-plan "${BUDGET_DIGEST}"
130+ node scripts/run-validation.js --route full --actor trusted-ci --authority-source "${AUTHORITY_SOURCE}" --policy-digest "${POLICY_DIGEST}" --approve-plan "${BUDGET_DIGEST}" --json > "${RUNNER_TEMP}/ci-validation-result-full.json"
68131 - run : npm run test:coverage
69132 - run : npm run test:audit
133+ - name : Preserve full validation receipt
134+ if : always()
135+ uses : actions/upload-artifact@v4
136+ with :
137+ name : ci-validation-result-full-${{ github.run_id }}-${{ github.run_attempt }}
138+ path : ${{ runner.temp }}/ci-validation-result-full.json
139+ if-no-files-found : error
140+ retention-days : 14
70141
71- website- package :
142+ package-boundary :
72143 name : Package boundary (Node 24.17)
144+ needs : plan
145+ if : needs.plan.outputs.run-package-boundary == 'true'
73146 runs-on : ubuntu-latest
74147 steps :
75148 - uses : actions/checkout@v6
@@ -80,3 +153,32 @@ jobs:
80153 - run : npm ci
81154 - run : npm run release:dry-run:all
82155 - run : npm run test:pack-clean
156+
157+ aggregate :
158+ name : Required validation aggregate
159+ if : always()
160+ needs : [plan, affected, full-quality, package-boundary]
161+ runs-on : ubuntu-latest
162+ steps :
163+ - uses : actions/checkout@v6
164+ - name : Download validation receipts
165+ if : needs.plan.result == 'success' && (needs.plan.outputs.run-impact-validation == 'true' || needs.plan.outputs.run-full-quality == 'true')
166+ uses : actions/download-artifact@v5
167+ with :
168+ pattern : ci-validation-result-*-${{ github.run_id }}-${{ github.run_attempt }}
169+ path : ${{ runner.temp }}/ci-validation-results
170+ merge-multiple : true
171+ - name : Verify required jobs and node receipts
172+ env :
173+ DEVCODEX_CI_PLAN_B64 : ${{ needs.plan.outputs.plan-b64 }}
174+ DEVCODEX_CI_JOB_RESULTS : >-
175+ {"plan":"${{ needs.plan.result }}","affected":"${{ needs.affected.result }}","fullQuality":"${{ needs['full-quality'].result }}","packageBoundary":"${{ needs['package-boundary'].result }}"}
176+ run : node scripts/plan-ci-validation.js aggregate --results-dir "${RUNNER_TEMP}/ci-validation-results" > "${RUNNER_TEMP}/ci-validation-aggregate.json"
177+ - name : Preserve aggregate receipt
178+ if : always()
179+ uses : actions/upload-artifact@v4
180+ with :
181+ name : ci-validation-aggregate-${{ github.run_id }}-${{ github.run_attempt }}
182+ path : ${{ runner.temp }}/ci-validation-aggregate.json
183+ if-no-files-found : warn
184+ retention-days : 14
0 commit comments