From 8921d6756c4440eedc20302f64071866a5769dfc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 21:19:48 +0900 Subject: [PATCH 01/11] test(actions): require reviewed Node 24 artifact pin --- test/upload-artifact-node24-integrity.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/upload-artifact-node24-integrity.test.ts diff --git a/test/upload-artifact-node24-integrity.test.ts b/test/upload-artifact-node24-integrity.test.ts new file mode 100644 index 000000000..db50c1454 --- /dev/null +++ b/test/upload-artifact-node24-integrity.test.ts @@ -0,0 +1,28 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const uploadArtifactPin = + "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1"; +const deprecatedUploadArtifactPin = + "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"; +const supportedWorkflowPaths = [ + ".github/workflows/acquisition-readiness-scan.yml", + ".github/workflows/cd.yml", + ".github/workflows/central-review.yml", + ".github/workflows/hourly-commercial-readiness.yml", + ".github/workflows/hourly-product-development.yml", + ".github/workflows/maintainer-app-readiness.yml", + ".github/workflows/readiness-scan.yml", + ".github/workflows/release-evidence.yml", +] as const; + +describe("upload-artifact Node 24 supply-chain contract", () => { + for (const workflowPath of supportedWorkflowPaths) { + it(`${workflowPath} pins the reviewed Node 24 upload action`, () => { + const workflow = readFileSync(workflowPath, "utf8"); + expect(workflow).toContain(uploadArtifactPin); + expect(workflow).not.toContain(deprecatedUploadArtifactPin); + expect(workflow).not.toMatch(/uses:\s+actions\/upload-artifact@v\d+/); + }); + } +}); From a7ecefb41d262d94b0c64d5ed1d060759d9409b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 22:19:32 +0900 Subject: [PATCH 02/11] fix(actions): migrate proven artifact uploads to Node 24 --- .github/workflows/acquisition-readiness-scan.yml | 2 +- .github/workflows/cd.yml | 2 +- .github/workflows/hourly-commercial-readiness.yml | 8 ++++---- .github/workflows/readiness-scan.yml | 2 +- .github/workflows/release-evidence.yml | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/acquisition-readiness-scan.yml b/.github/workflows/acquisition-readiness-scan.yml index a825687a5..91cf0d186 100644 --- a/.github/workflows/acquisition-readiness-scan.yml +++ b/.github/workflows/acquisition-readiness-scan.yml @@ -61,7 +61,7 @@ jobs: - name: upload acquisition artifacts if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: acquisition-readiness-audit path: artifacts/acquisition-readiness/**/* diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2df1ccbd8..1d3ba4584 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -260,7 +260,7 @@ jobs: - name: Upload durable deployment and rollback evidence if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-deployment-evidence-production-${{ steps.release.outputs.tag }} path: | diff --git a/.github/workflows/hourly-commercial-readiness.yml b/.github/workflows/hourly-commercial-readiness.yml index 294debefc..91657db0c 100644 --- a/.github/workflows/hourly-commercial-readiness.yml +++ b/.github/workflows/hourly-commercial-readiness.yml @@ -107,7 +107,7 @@ jobs: - name: upload scheduler activation evidence if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: scheduler-activation-evidence path: artifacts/operations/hourly-scheduler-activation.json @@ -199,7 +199,7 @@ jobs: - name: upload main governance audit if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: main-governance-audit path: artifacts/governance/main-governance-audit.json @@ -208,7 +208,7 @@ jobs: - name: upload loop report if: always() && steps.loop.outcome != 'skipped' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: commercial-readiness-loop-report path: artifacts/commercial-readiness/hourly-loop-report.json @@ -217,7 +217,7 @@ jobs: - name: upload no-PR commercial-readiness evidence if: always() && steps.loop.outputs.remaining_open_pull_request_count == '0' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: no-pr-commercial-readiness-evidence path: | diff --git a/.github/workflows/readiness-scan.yml b/.github/workflows/readiness-scan.yml index 2b98770e6..72e0e395d 100644 --- a/.github/workflows/readiness-scan.yml +++ b/.github/workflows/readiness-scan.yml @@ -80,7 +80,7 @@ jobs: - name: upload readiness artifacts if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: saleable-readiness-audit path: | diff --git a/.github/workflows/release-evidence.yml b/.github/workflows/release-evidence.yml index 439d59851..6b546fa87 100644 --- a/.github/workflows/release-evidence.yml +++ b/.github/workflows/release-evidence.yml @@ -234,7 +234,7 @@ jobs: - name: Upload bounded sterile release materialization id: materialization_bundle - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-release-materialization-${{ needs.verify_release.outputs.tag }} path: | @@ -404,7 +404,7 @@ jobs: - name: Upload bounded release publication bundle id: release_bundle - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-release-evidence-${{ needs.materialize_release.outputs.tag }} path: | @@ -647,7 +647,7 @@ jobs: - name: Upload durable publication receipt if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-release-publication-${{ needs.attest_release.outputs.tag }} path: ${{ runner.temp }}/release-publication/ From 0f0e59d41ccf04d807f9ac209fd9bf9c997e4a01 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 23:02:43 +0900 Subject: [PATCH 03/11] test(actions): require exact upload-artifact inventory pins --- test/upload-artifact-node24-integrity.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/upload-artifact-node24-integrity.test.ts b/test/upload-artifact-node24-integrity.test.ts index db50c1454..04b8c26ed 100644 --- a/test/upload-artifact-node24-integrity.test.ts +++ b/test/upload-artifact-node24-integrity.test.ts @@ -18,9 +18,17 @@ const supportedWorkflowPaths = [ describe("upload-artifact Node 24 supply-chain contract", () => { for (const workflowPath of supportedWorkflowPaths) { - it(`${workflowPath} pins the reviewed Node 24 upload action`, () => { + it(`${workflowPath} pins every upload-artifact use to the reviewed Node 24 action`, () => { const workflow = readFileSync(workflowPath, "utf8"); - expect(workflow).toContain(uploadArtifactPin); + const uploadArtifactLines = workflow + .split(/\r?\n/) + .filter((line) => line.includes("actions/upload-artifact@")) + .map((line) => line.trim()); + + expect(uploadArtifactLines.length).toBeGreaterThan(0); + expect(uploadArtifactLines).toEqual( + uploadArtifactLines.map(() => `uses: ${uploadArtifactPin}`), + ); expect(workflow).not.toContain(deprecatedUploadArtifactPin); expect(workflow).not.toMatch(/uses:\s+actions\/upload-artifact@v\d+/); }); From 36fa5ce18253300c98f4eeb87f821a896894dad5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:05:26 +0900 Subject: [PATCH 04/11] test(actions): align readiness upload-artifact pin --- test/readiness-scan-toolchain-integrity.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/readiness-scan-toolchain-integrity.test.ts b/test/readiness-scan-toolchain-integrity.test.ts index a5942a5fb..886310745 100644 --- a/test/readiness-scan-toolchain-integrity.test.ts +++ b/test/readiness-scan-toolchain-integrity.test.ts @@ -12,7 +12,7 @@ describe("saleable-readiness workflow supply-chain integrity", () => { "uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0", ); expect(workflow).toContain( - "uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2", + "uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1", ); expect(workflow).not.toMatch(/uses:\s+actions\/(?:checkout|setup-node|upload-artifact)@v\d+/); }); From 410e58dc8290412e59900faed5cc66695a864469 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:07:23 +0900 Subject: [PATCH 05/11] test(actions): align acquisition artifact pin --- test/acquisition-readiness-scan-toolchain-integrity.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/acquisition-readiness-scan-toolchain-integrity.test.ts b/test/acquisition-readiness-scan-toolchain-integrity.test.ts index d5453c355..2cb289c13 100644 --- a/test/acquisition-readiness-scan-toolchain-integrity.test.ts +++ b/test/acquisition-readiness-scan-toolchain-integrity.test.ts @@ -16,7 +16,7 @@ describe("acquisition-readiness workflow supply-chain integrity", () => { "uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0", ); expect(workflow).toContain( - "uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2", + "uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1", ); expect(workflow).not.toMatch( /uses:\s+actions\/(?:checkout|setup-node|upload-artifact)@v\d+/, From 4d756b7644c3550d8c4fc184b1a1e114596b571c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:08:41 +0900 Subject: [PATCH 06/11] build(actions): upgrade maintainer artifact uploads --- .github/workflows/maintainer-app-readiness.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maintainer-app-readiness.yml b/.github/workflows/maintainer-app-readiness.yml index fdbe6fc9c..d5de37e4a 100644 --- a/.github/workflows/maintainer-app-readiness.yml +++ b/.github/workflows/maintainer-app-readiness.yml @@ -174,7 +174,7 @@ jobs: - name: upload main governance evidence if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: main-governance-audit path: ${{ runner.temp }}/noema-maintainer-app-readiness/main-governance-audit.json @@ -183,7 +183,7 @@ jobs: - name: upload Maintainer App readiness evidence if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: maintainer-app-readiness path: ${{ runner.temp }}/noema-maintainer-app-readiness/maintainer-app-readiness.json @@ -192,7 +192,7 @@ jobs: - name: upload no-write commercial loop evidence if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: commercial-readiness-loop-dry-run path: ${{ runner.temp }}/noema-maintainer-app-readiness/commercial-readiness-loop-dry-run.json From 9febd28819068a99733797ef07ee24aa2356a2f6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:11:10 +0900 Subject: [PATCH 07/11] build(actions): upgrade central review artifact uploads --- .github/workflows/central-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/central-review.yml b/.github/workflows/central-review.yml index 5ae625105..2599a75a6 100644 --- a/.github/workflows/central-review.yml +++ b/.github/workflows/central-review.yml @@ -262,7 +262,7 @@ jobs: sha256sum noema-manifest.json >noema-manifest.sha256 - name: Upload short-lived bounded review manifest - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-manifest-${{ steps.target.outputs.head_sha }} path: | @@ -324,7 +324,7 @@ jobs: "$RUNNER_TEMP/noema-manifest-attestation.json" - name: Upload short-lived signed manifest attestation - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-manifest-attestation-${{ needs.collect_evidence.outputs.head_sha }} path: ${{ runner.temp }}/noema-manifest-attestation.json From 91231c31b0330cab1aa7381b2d9c3fc7ebbcca62 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 04:59:01 +0900 Subject: [PATCH 08/11] fix(actions): update product artifact runtime --- .github/workflows/hourly-product-development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hourly-product-development.yml b/.github/workflows/hourly-product-development.yml index 7e1201a80..959e9feaf 100644 --- a/.github/workflows/hourly-product-development.yml +++ b/.github/workflows/hourly-product-development.yml @@ -460,7 +460,7 @@ jobs: - name: Upload bounded proposal patch id: upload_proposal if: steps.proposal.outputs.has_changes == 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-product-proposal-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ runner.temp }}/noema-product-proposal/proposal.patch From a3c718a2d7aadd71e93a5c3e5eea05297e52cf4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 07:03:00 +0900 Subject: [PATCH 09/11] test(actions): align product workflow artifact pin assertion --- test/hourly-product-development-workflow.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hourly-product-development-workflow.test.ts b/test/hourly-product-development-workflow.test.ts index a234e35ab..3c250c057 100644 --- a/test/hourly-product-development-workflow.test.ts +++ b/test/hourly-product-development-workflow.test.ts @@ -111,7 +111,7 @@ describe("hourly NVIDIA NIM OpenCode product-development workflow", () => { expect(tokenIndex).toBeLessThan(revalidationIndex); expect(workflow).toContain( - "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02", + "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1", ); expect(workflow).toContain( "actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093", @@ -231,7 +231,7 @@ describe("hourly NVIDIA NIM OpenCode product-development workflow", () => { /DEPENDENCY_REINSTALL_TIMEOUT_SECONDS: "(\d+)"/, ); const reinstallGraceMatch = workflow.match( - /DEPENDENCY_REINSTALL_KILL_GRACE_SECONDS: "(\d+)"/, + /DEPENDENDENCY_REINSTALL_KILL_GRACE_SECONDS: "(\d+)"/, ); const jobTimeoutMatch = proposer.match(/timeout-minutes: (\d+)/); const candidateCount = workflow.match(/^ nvidia-nim\/.+$/gm)?.length ?? 0; From 532d9715d484b7c00c08a09eab780c265bc22a31 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 07:04:09 +0900 Subject: [PATCH 10/11] test(actions): restore dependency cleanup budget assertion --- test/hourly-product-development-workflow.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hourly-product-development-workflow.test.ts b/test/hourly-product-development-workflow.test.ts index 3c250c057..4988af18e 100644 --- a/test/hourly-product-development-workflow.test.ts +++ b/test/hourly-product-development-workflow.test.ts @@ -231,7 +231,7 @@ describe("hourly NVIDIA NIM OpenCode product-development workflow", () => { /DEPENDENCY_REINSTALL_TIMEOUT_SECONDS: "(\d+)"/, ); const reinstallGraceMatch = workflow.match( - /DEPENDENDENCY_REINSTALL_KILL_GRACE_SECONDS: "(\d+)"/, + /DEPENDENCY_REINSTALL_KILL_GRACE_SECONDS: "(\d+)"/, ); const jobTimeoutMatch = proposer.match(/timeout-minutes: (\d+)/); const candidateCount = workflow.match(/^ nvidia-nim\/.+$/gm)?.length ?? 0; From 8b5b89a821dc71e9e2957311805aa1bf4d56bb8e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 11:06:09 +0900 Subject: [PATCH 11/11] test(actions): bind upload-artifact pin to full workflow inventory --- test/upload-artifact-node24-integrity.test.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/upload-artifact-node24-integrity.test.ts b/test/upload-artifact-node24-integrity.test.ts index 04b8c26ed..25e19f333 100644 --- a/test/upload-artifact-node24-integrity.test.ts +++ b/test/upload-artifact-node24-integrity.test.ts @@ -1,10 +1,11 @@ -import { readFileSync } from "node:fs"; +import { readFileSync, readdirSync } from "node:fs"; import { describe, expect, it } from "vitest"; const uploadArtifactPin = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1"; const deprecatedUploadArtifactPin = "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"; +const workflowDirectory = ".github/workflows"; const supportedWorkflowPaths = [ ".github/workflows/acquisition-readiness-scan.yml", ".github/workflows/cd.yml", @@ -16,7 +17,24 @@ const supportedWorkflowPaths = [ ".github/workflows/release-evidence.yml", ] as const; +function workflowPathsUsingUploadArtifact(): string[] { + return readdirSync(workflowDirectory, { withFileTypes: true }) + .filter( + (entry) => + entry.isFile() && (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml")), + ) + .map((entry) => `${workflowDirectory}/${entry.name}`) + .filter((workflowPath) => + readFileSync(workflowPath, "utf8").includes("actions/upload-artifact@"), + ) + .sort(); +} + describe("upload-artifact Node 24 supply-chain contract", () => { + it("keeps the reviewed workflow inventory complete", () => { + expect(workflowPathsUsingUploadArtifact()).toEqual([...supportedWorkflowPaths].sort()); + }); + for (const workflowPath of supportedWorkflowPaths) { it(`${workflowPath} pins every upload-artifact use to the reviewed Node 24 action`, () => { const workflow = readFileSync(workflowPath, "utf8");