From 70f1c0b637cfa27ea43ee79d27fe2c4640f6be97 Mon Sep 17 00:00:00 2001 From: aaronormerod <137795541+aaronormerod@users.noreply.github.com> Date: Mon, 12 Oct 2026 07:45:45 +0100 Subject: [PATCH 1/2] Run AGT pin check and upstream watch in CI Closes #74 Signed-off-by: aaronormerod <137795541+aaronormerod@users.noreply.github.com> --- .../reference-implementation-supply-chain.yml | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/reference-implementation-supply-chain.yml diff --git a/.github/workflows/reference-implementation-supply-chain.yml b/.github/workflows/reference-implementation-supply-chain.yml new file mode 100644 index 00000000..e8991e4c --- /dev/null +++ b/.github/workflows/reference-implementation-supply-chain.yml @@ -0,0 +1,83 @@ +# Runs the AGT supply-chain checks that nothing else invokes. +# +# verify-pin.sh clones agt.lock's pinned commit and byte-diffs the vendored policy +# bundle against it. A mismatch fails the job. +# +# run-upstream-watch.sh diffs AGT's contract surfaces between the pinned commit and +# upstream main, and prints a report. Drift exits 0 because moving the pin is a human +# call. The job fails only when the watch could not run. The report goes to the job +# summary. +# +# verify:zero-diff is left out. It compares against a branch that does not exist in +# this repository and exits 1 by design here. +# +# Both scripts clone from GitHub, so this is its own workflow. The test job in +# reference-implementation.yml should not depend on the network. +# +# No workflow expression appears inside a `run:` block. +name: Reference implementation supply chain + +on: + pull_request: + paths: + - "reference-implementations/agt/agt.lock" + - "reference-implementations/agt/policy/**" + - "reference-implementations/agt/scripts/verify-pin.sh" + - "reference-implementations/agt/scripts/run-upstream-watch.sh" + - ".github/workflows/reference-implementation-supply-chain.yml" + push: + branches: ["main", "integration"] + paths: + - "reference-implementations/agt/agt.lock" + - "reference-implementations/agt/policy/**" + - "reference-implementations/agt/scripts/verify-pin.sh" + - "reference-implementations/agt/scripts/run-upstream-watch.sh" + - ".github/workflows/reference-implementation-supply-chain.yml" + schedule: + # Weekly, an hour after zizmor. Upstream moves whether or not this tree changes. + - cron: "0 7 * * 1" + workflow_dispatch: + +permissions: {} + +concurrency: + group: reference-implementation-supply-chain-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + agt: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + defaults: + run: + working-directory: reference-implementations/agt + steps: + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # Same pin as reference-implementation.yml. + - name: Install Bun + uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2 + with: + bun-version: "1.3.11" + + - name: Install dependencies from the lockfile + run: bun install --frozen-lockfile + + # Both scripts clean up with `trash` and refuse to start without it. Same pin + # as reference-implementation.yml. + - name: Install the trash CLI the scripts require + run: bun add --global trash-cli@7.2.0 + + - name: Verify the vendored bundle against the pinned commit + run: bash scripts/verify-pin.sh + + # pipefail so a failed watch fails the step instead of tee's exit status. + - name: Report drift against upstream main + run: | + set -euo pipefail + bash scripts/run-upstream-watch.sh | tee -a "$GITHUB_STEP_SUMMARY" From 9298d10c06ca13a93ed9fbde3719a1c8c261f2f2 Mon Sep 17 00:00:00 2001 From: aaronormerod <137795541+aaronormerod@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:50:12 +0000 Subject: [PATCH 2/2] Address review: check agt_repo, rename job Add a step that fails when agt.lock names a repository other than microsoft/agent-governance-toolkit. Rename the job to agt-supply-chain. State what verify-pin.sh skips. Correct the conformance README: the watch fails the job when it could not run. Signed-off-by: aaronormerod <137795541+aaronormerod@users.noreply.github.com> (cherry picked from commit f15768bbcdfbd893869bdf9af6496e708e2e0026) --- .../reference-implementation-supply-chain.yml | 21 ++++++++++++++++--- .../agt/packages/conformance/README.md | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reference-implementation-supply-chain.yml b/.github/workflows/reference-implementation-supply-chain.yml index e8991e4c..1142f864 100644 --- a/.github/workflows/reference-implementation-supply-chain.yml +++ b/.github/workflows/reference-implementation-supply-chain.yml @@ -1,7 +1,8 @@ # Runs the AGT supply-chain checks that nothing else invokes. # -# verify-pin.sh clones agt.lock's pinned commit and byte-diffs the vendored policy -# bundle against it. A mismatch fails the job. +# verify-pin.sh clones agt.lock's pinned commit and byte-diffs the vendored .rego +# files against it. A mismatch fails the job. It skips the other files in policy/lib, +# including data.json, which holds the egress allowlist. # # run-upstream-watch.sh diffs AGT's contract surfaces between the pinned commit and # upstream main, and prints a report. Drift exits 0 because moving the pin is a human @@ -45,7 +46,7 @@ concurrency: cancel-in-progress: true jobs: - agt: + agt-supply-chain: runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -59,6 +60,20 @@ jobs: with: persist-credentials: false + # agt.lock comes from the pull request, and both scripts clone whatever agt_repo + # names. The expected value lives here because /.github/ needs a project lead's + # review. This checks the repository only. agt_ref still comes from the pull + # request, so a reviewer has to read every change to it. + - name: Check that agt.lock names the upstream repository + run: | + set -euo pipefail + expected="https://github.com/microsoft/agent-governance-toolkit" + actual="$(jq -r '.agt_repo' agt.lock)" + if [ "$actual" != "$expected" ]; then + echo "agt.lock names $actual, expected $expected" >&2 + exit 1 + fi + # Same pin as reference-implementation.yml. - name: Install Bun uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2 diff --git a/reference-implementations/agt/packages/conformance/README.md b/reference-implementations/agt/packages/conformance/README.md index 62087828..9081b9ec 100644 --- a/reference-implementations/agt/packages/conformance/README.md +++ b/reference-implementations/agt/packages/conformance/README.md @@ -48,7 +48,7 @@ bun run watch:upstream This runs `scripts/run-upstream-watch.sh`, which clones AGT twice, once at the pinned commit and once at `main`, and hands the two paths to the runner as `PINNED_AGT_CLONE` and `UPSTREAM_AGT_CLONE`. The runner diffs AGT's eight declared contract surfaces field by field, validates the Guardian's policy input against both schema versions, and reports which tools the two hookmaps declare against the manifest's registry. -The watch reports. It never fails a build. Nothing reaches this tree until a human moves the pin in `agt.lock`. +The watch reports drift, and drift never fails a build. A run that could not finish does fail the job. Nothing reaches this tree until a human moves the pin in `agt.lock`. ## Use it in-process