Skip to content
Closed
31 changes: 24 additions & 7 deletions .github/workflows/acquisition-readiness-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,33 @@ jobs:

steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "24"
node-version: "24.19.0"
cache: npm

- name: install
run: npm ci
- name: verify package-manager toolchain
shell: bash
run: |
set -euo pipefail
test "$(node --version)" = "v24.19.0"
test "$(npm --version)" = "11.17.0"

- name: verify tracked acquisition source before evidence generation
shell: bash
run: |
set -euo pipefail
tracked_drift="$(git status --porcelain=v1 --untracked-files=no)"
if [ -n "$tracked_drift" ]; then
printf '::error::Tracked acquisition source changed before evidence generation:\n%s\n' "$tracked_drift"
exit 1
fi
test "$(git rev-parse HEAD)" = "${{ github.sha }}"

- name: build data-room manifest
run: npm run acquisition:manifest
Expand All @@ -44,8 +61,8 @@ jobs:

- name: upload acquisition artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: acquisition-readiness-audit
path: artifacts/acquisition-readiness/**/*
if-no-files-found: warn
if-no-files-found: error
57 changes: 57 additions & 0 deletions test/acquisition-readiness-scan-toolchain-integrity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

const workflow = readFileSync(
".github/workflows/acquisition-readiness-scan.yml",
"utf8",
);

describe("acquisition-readiness workflow supply-chain integrity", () => {
it("pins trusted actions and refuses persisted checkout credentials", () => {
expect(workflow).toContain(
"uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2",
);
expect(workflow).toContain("persist-credentials: false");
expect(workflow).toContain(
"uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0",
);
expect(workflow).toContain(
"uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2",
);
expect(workflow).not.toMatch(
/uses:\s+actions\/(?:checkout|setup-node|upload-artifact)@v\d+/,
);
});

it("uses exact Node/npm identities without installing dependency code", () => {
expect(workflow).toContain('node-version: "24.19.0"');
expect(workflow).toContain('test "$(node --version)" = "v24.19.0"');
expect(workflow).toContain('test "$(npm --version)" = "11.17.0"');
expect(workflow).not.toContain('node-version: "24"');
expect(workflow).not.toMatch(/\bnpm\s+(?:ci|install|i)\b/);
expect(workflow).not.toContain(" - name: install");
});

it("revalidates tracked buyer-evidence source before evidence generation", () => {
const integrityIndex = workflow.indexOf(
" - name: verify tracked acquisition source before evidence generation",
);
const manifestIndex = workflow.indexOf(" - name: build data-room manifest");

expect(integrityIndex).toBeGreaterThan(-1);
expect(manifestIndex).toBeGreaterThan(integrityIndex);

const integrityBlock = workflow.slice(integrityIndex, manifestIndex);
expect(integrityBlock).toContain("git status --porcelain=v1 --untracked-files=no");
expect(integrityBlock).toContain('git rev-parse HEAD');
expect(integrityBlock).toContain('github.sha');
});

it("fails closed when retained acquisition evidence is missing", () => {
const uploadIndex = workflow.indexOf(" - name: upload acquisition artifacts");
expect(uploadIndex).toBeGreaterThan(-1);
const uploadBlock = workflow.slice(uploadIndex);
expect(uploadBlock).toContain("if-no-files-found: error");
expect(uploadBlock).not.toContain("if-no-files-found: warn");
});
});
Loading