Skip to content

chore(deps): update anthropics/skills digest to d230a6d #205

chore(deps): update anthropics/skills digest to d230a6d

chore(deps): update anthropics/skills digest to d230a6d #205

Workflow file for this run

name: Build Skill Artifacts
on:
push:
branches: [ main ]
paths:
- 'skills/**/*.yaml'
- 'cmd/dockhand/**'
- 'internal/skills/**'
- 'scripts/skill-scan/**'
- '.github/workflows/build-skills.yml'
- '.github/workflows/skill-scan-report.yml'
- 'go.mod'
- 'go.sum'
pull_request:
branches: [ main ]
paths:
- 'skills/**/*.yaml'
- 'cmd/dockhand/**'
- 'internal/skills/**'
- 'scripts/skill-scan/**'
- '.github/workflows/build-skills.yml'
- '.github/workflows/skill-scan-report.yml'
- 'go.mod'
- 'go.sum'
workflow_dispatch:
permissions: {}
env:
REGISTRY: ghcr.io
jobs:
discover-skill-configs:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed-configs: ${{ steps.find-configs.outputs.changed-configs }}
scan-configs: ${{ steps.find-configs.outputs.scan-configs }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Find skill configurations to build
id: find-configs
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
run: |
# Find all skill spec.yaml files
all_configs=$(find skills -name "spec.yaml" -type f 2>/dev/null | sort)
if [ -z "$all_configs" ]; then
echo "No skill configurations found"
echo "changed-configs=[]" >> $GITHUB_OUTPUT
echo "scan-configs=[]" >> $GITHUB_OUTPUT
exit 0
fi
if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
configs_to_build="$all_configs"
configs_to_scan="$all_configs"
echo "Manual trigger - building all skill configurations"
elif [ "$EVENT_NAME" == "pull_request" ]; then
changed_files=$(git diff --name-only origin/"$BASE_REF"...HEAD)
configs_to_build=""
configs_to_scan=""
# Scan only specs that actually changed; build the superset below.
for config in $all_configs; do
config_dir=$(dirname "$config")
if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then
configs_to_build="$configs_to_build$config"$'\n'
configs_to_scan="$configs_to_scan$config"$'\n'
fi
done
# If core build inputs changed, rebuild all skills — but only scan specs that truly changed.
if echo "$changed_files" | grep -E "(cmd/dockhand/|internal/skills/|scripts/skill-scan/|\.github/workflows/build-skills\.yml|go\.mod|go\.sum)"; then
echo "Core files changed - rebuilding all skill configurations (scan only changed specs)"
configs_to_build="$all_configs"
fi
else
changed_files=$(git diff --name-only HEAD~1..HEAD)
configs_to_build=""
configs_to_scan=""
for config in $all_configs; do
config_dir=$(dirname "$config")
if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then
configs_to_build="$configs_to_build$config"$'\n'
configs_to_scan="$configs_to_scan$config"$'\n'
fi
done
if echo "$changed_files" | grep -E "(cmd/dockhand/|internal/skills/|scripts/skill-scan/|\.github/workflows/build-skills\.yml|go\.mod|go\.sum)"; then
echo "Core files changed - rebuilding all skill configurations (scan only changed specs)"
configs_to_build="$all_configs"
fi
fi
configs_json=$(echo "$configs_to_build" | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]')
scan_configs_json=$(echo "$configs_to_scan" | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]')
echo "changed-configs=$configs_json" >> $GITHUB_OUTPUT
echo "scan-configs=$scan_configs_json" >> $GITHUB_OUTPUT
echo "Skill configurations to build: $configs_json"
echo "Skill configurations to scan: $scan_configs_json"
validate-skills:
needs: discover-skill-configs
runs-on: ubuntu-latest
if: ${{ needs.discover-skill-configs.outputs.changed-configs != '[]' }}
strategy:
matrix:
config: ${{ fromJson(needs.discover-skill-configs.outputs.changed-configs) }}
fail-fast: false
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: 'go.mod'
- name: Build dockhand
run: go build -o /tmp/dockhand ./cmd/dockhand
- name: Validate skill
env:
CONFIG_FILE: ${{ matrix.config }}
run: |
echo "Validating skill: $CONFIG_FILE"
/tmp/dockhand validate-skill --config "$CONFIG_FILE"
skill-security-scan:
needs: discover-skill-configs
runs-on: ubuntu-latest
timeout-minutes: 15
if: ${{ needs.discover-skill-configs.outputs.scan-configs != '[]' }}
strategy:
matrix:
config: ${{ fromJson(needs.discover-skill-configs.outputs.scan-configs) }}
fail-fast: false
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install yq
uses: mikefarah/yq@751d8ad57b84f1794661bc70c0afb92a22ad7b3c # v4.53.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.13'
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
- name: Install dependencies
id: install-deps
run: |
uv pip install --system -r scripts/skill-scan/requirements.txt
skill-scanner --version || true
SCANNER_VERSION=$(uv pip show cisco-ai-skill-scanner | grep "^Version:" | cut -d' ' -f2)
echo "scanner_version=$SCANNER_VERSION" >> $GITHUB_OUTPUT
echo "Installed skill-scanner version: $SCANNER_VERSION"
- name: Extract metadata from config
id: meta
env:
CONFIG_FILE: ${{ matrix.config }}
run: |
skill_name=$(echo "$CONFIG_FILE" | cut -d'/' -f2)
echo "skill_name=$skill_name" >> $GITHUB_OUTPUT
echo "config_file=$CONFIG_FILE" >> $GITHUB_OUTPUT
repository=$(yq '.spec.repository' "$CONFIG_FILE")
ref=$(yq '.spec.ref' "$CONFIG_FILE")
skill_path=$(yq '.spec.path // ""' "$CONFIG_FILE")
echo "repository=$repository" >> $GITHUB_OUTPUT
echo "ref=$ref" >> $GITHUB_OUTPUT
echo "skill_path=$skill_path" >> $GITHUB_OUTPUT
- name: Check out skill source
id: skill-src
env:
SKILL_REPO: ${{ steps.meta.outputs.repository }}
SKILL_REF: ${{ steps.meta.outputs.ref }}
SKILL_PATH: ${{ steps.meta.outputs.skill_path }}
SKILL_NAME: ${{ steps.meta.outputs.skill_name }}
run: |
workdir="/tmp/skill-scan-${SKILL_NAME}"
rm -rf "$workdir"
mkdir -p "$workdir"
git clone --filter=tree:0 --no-checkout --quiet "$SKILL_REPO" "$workdir/repo"
git -C "$workdir/repo" checkout --quiet "$SKILL_REF"
if [ -n "$SKILL_PATH" ]; then
src_dir="$workdir/repo/$SKILL_PATH"
else
src_dir="$workdir/repo"
fi
if [ ! -d "$src_dir" ]; then
echo "Error: skill source directory not found at $src_dir" >&2
exit 1
fi
echo "source_dir=$src_dir" >> $GITHUB_OUTPUT
- name: Run skill security scan
id: scan
env:
# LLM analyzer (LiteLLM-driven). Reuses the same provider key as the
# MCP scanner — single secret across both pipelines, single rotation.
# Toggle via repo variable SKILL_SCANNER_USE_LLM=false to disable in a pinch.
SKILL_SCANNER_USE_LLM: ${{ vars.SKILL_SCANNER_USE_LLM || 'true' }}
SKILL_SCANNER_LLM_API_KEY: ${{ secrets.MCP_SCANNER_LLM_API_KEY }}
# Dedicated model knob — set to e.g. anthropic/claude-sonnet-4. If unset,
# skill-scanner falls back to its built-in default (claude-3-5-sonnet-...).
SKILL_SCANNER_LLM_MODEL: ${{ vars.SKILL_SCANNER_LLM_MODEL }}
# Optional consensus voting — N>1 multiplies LLM cost per scan.
SKILL_SCANNER_LLM_CONSENSUS_RUNS: ${{ vars.SKILL_SCANNER_LLM_CONSENSUS_RUNS }}
# Severity threshold for blocking. Findings below this severity are
# surfaced as warnings but do not fail the job. One of: INFO, LOW,
# MEDIUM, HIGH, CRITICAL. Tune via PR.
SKILL_SCANNER_BLOCK_SEVERITY: HIGH
SKILL_NAME: ${{ steps.meta.outputs.skill_name }}
SOURCE_DIR: ${{ steps.skill-src.outputs.source_dir }}
CONFIG_FILE: ${{ matrix.config }}
SCANNER_VERSION: ${{ steps.install-deps.outputs.scanner_version }}
run: |
scan_output="skill-scan-${SKILL_NAME}.json"
scan_stderr="skill-scan-${SKILL_NAME}.stderr"
python3 scripts/skill-scan/run_scan.py \
--source "$SOURCE_DIR" \
--output "$scan_output" \
2> "$scan_stderr" || true
if [ -s "$scan_stderr" ]; then
echo "Scanner stderr output:"
cat "$scan_stderr"
fi
# process_scan_results.py exits 1 on unallowlisted findings — that fails the job.
python3 scripts/skill-scan/process_scan_results.py \
"$scan_output" \
"$SKILL_NAME" \
"$CONFIG_FILE" \
> scan-summary.json
echo "$SCANNER_VERSION" > scanner-version.txt
- name: Upload scan results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: skill-scan-${{ steps.meta.outputs.skill_name }}
path: |
skill-scan-${{ steps.meta.outputs.skill_name }}.json
scan-summary.json
scanner-version.txt
retention-days: 30
build-skill-artifacts:
needs: [discover-skill-configs, validate-skills, skill-security-scan]
runs-on: ubuntu-latest
timeout-minutes: 30
# Aggregate scan result is intentionally not gated here. A failure in one
# skill's scan must not block publishing the rest. The per-cell
# `Pre-flight scan gate` step below preserves the security gate at the
# individual skill level by reading scan-summary.json from the artifact
# produced by skill-security-scan (uploaded with `if: always()`).
if: ${{ !cancelled() && needs.discover-skill-configs.outputs.changed-configs != '[]' && needs.validate-skills.result == 'success' }}
strategy:
matrix:
config: ${{ fromJson(needs.discover-skill-configs.outputs.changed-configs) }}
fail-fast: false
permissions:
contents: read
packages: write
id-token: write # Needed for OIDC token (cosign keyless signing, attestations)
attestations: write # Needed for actions/attest-* to publish attestations
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: 'go.mod'
- name: Install Cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Install yq
uses: mikefarah/yq@751d8ad57b84f1794661bc70c0afb92a22ad7b3c # v4.53.2
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata from config
id: meta
env:
CONFIG_FILE: ${{ matrix.config }}
run: |
echo "config_file=$CONFIG_FILE" >> $GITHUB_OUTPUT
skill_name=$(echo "$CONFIG_FILE" | cut -d'/' -f2)
echo "skill_name=$skill_name" >> $GITHUB_OUTPUT
version=$(yq '.spec.version' "$CONFIG_FILE" 2>/dev/null || echo "latest")
echo "version=$version" >> $GITHUB_OUTPUT
ref=$(yq '.spec.ref' "$CONFIG_FILE" 2>/dev/null || echo "")
echo "ref=$ref" >> $GITHUB_OUTPUT
repository=$(yq '.spec.repository' "$CONFIG_FILE" 2>/dev/null || echo "")
echo "repository=$repository" >> $GITHUB_OUTPUT
skill_path=$(yq '.spec.path // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
echo "skill_path=$skill_path" >> $GITHUB_OUTPUT
image_name="${REGISTRY}/${{ github.repository }}/skills/${skill_name}"
echo "image_name=$image_name" >> $GITHUB_OUTPUT
# Pull the per-skill security scan artifact early, before we spend time
# building. `continue-on-error: true` so this step succeeds when the
# artifact does not exist (i.e. this skill was not in scan-configs for
# this run); the next step decides whether that is acceptable.
- name: Download skill security scan results
id: download-scan
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: skill-scan-${{ steps.meta.outputs.skill_name }}
path: /tmp/skill-scan-results
continue-on-error: true
# Per-cell scan gate. Replaces the old aggregate
# `needs.skill-security-scan.result == 'success'` check at the job
# level so that one skill's scan failure cannot starve out the rest.
# - If this skill was in scan-configs for this run, require a fresh
# scan-summary.json with status in {passed, warning}. Anything else
# fails the cell, preserving the security gate per skill.
# - If this skill was NOT in scan-configs (e.g. go.mod-only push that
# rebuilds everything but only re-scans changed specs), proceed on
# prior trust: the previous run on main must have been clean for the
# pinned ref to land here.
- name: Pre-flight scan gate
env:
CONFIG: ${{ matrix.config }}
SCAN_CONFIGS: ${{ needs.discover-skill-configs.outputs.scan-configs }}
SKILL_NAME: ${{ steps.meta.outputs.skill_name }}
run: |
set -euo pipefail
summary=/tmp/skill-scan-results/scan-summary.json
expected=$(jq -r --arg c "$CONFIG" 'index($c) // "missing"' <<<"$SCAN_CONFIGS")
if [ ! -f "$summary" ]; then
if [ "$expected" = "missing" ]; then
echo "No scan ran for ${SKILL_NAME} in this workflow (not in scan-configs); proceeding on prior trust."
exit 0
fi
echo "::error title=Scan artifact missing::scan-summary.json absent for ${SKILL_NAME} but the skill was in scan-configs; refusing to publish."
exit 1
fi
status=$(jq -r '.status // "missing"' "$summary")
case "$status" in
passed|warning)
echo "Skill scan status for ${SKILL_NAME}: ${status} - publish allowed."
;;
*)
echo "::error title=Scan blocked publish::Skill ${SKILL_NAME} scan status is '${status}'; refusing to publish."
jq -r '.blocking_issues[]? | " - [\(.code)] (\(.severity)) \(.message)"' "$summary" 2>/dev/null || true
exit 1
;;
esac
- name: Build dockhand
run: go build -o /tmp/dockhand ./cmd/dockhand
- name: Build skill artifact
id: build
env:
CONFIG_FILE: ${{ steps.meta.outputs.config_file }}
IMAGE_REF: ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.version }}
PUSH: ${{ github.event_name != 'pull_request' }}
run: |
# pipefail so a dockhand failure propagates through `tee` and fails
# the step. Without it, `set -e` alone would not fail on a non-zero
# left-hand side of the pipeline.
set -o pipefail
echo "Building skill artifact for $CONFIG_FILE"
build_args="--config $CONFIG_FILE --tag $IMAGE_REF"
if [ "$PUSH" = "true" ]; then
build_args="$build_args --push"
fi
log_file=$(mktemp)
# Retry dockhand on non-zero exit. Matrix jobs concurrently pushing
# brand-new GHCR packages regularly hit transient 5xx from the
# registry (observed "500 unknown: unknown error" on manifest PUT).
# A linear backoff is enough — a successful retry typically lands
# within the first extra attempt.
max_attempts=3
attempt=1
while :; do
echo "=== dockhand build-skill attempt ${attempt}/${max_attempts} ==="
if /tmp/dockhand build-skill $build_args 2>&1 | tee "$log_file"; then
break
fi
if [ "$attempt" -ge "$max_attempts" ]; then
echo "dockhand build-skill failed after ${attempt} attempt(s)" >&2
exit 1
fi
backoff=$(( 10 * attempt ))
echo "dockhand build-skill failed; retrying in ${backoff}s..." >&2
sleep "$backoff"
attempt=$(( attempt + 1 ))
done
# Extract digest from captured output. `|| true` so an absent
# "Digest:" line yields digest="" without failing the step; downstream
# steps already gate on `steps.build.outputs.digest != ''`.
digest=$(grep "^Digest:" "$log_file" | awk '{print $2}' || true)
echo "digest=$digest" >> $GITHUB_OUTPUT
rm -f "$log_file"
- name: Sign skill artifact with Cosign
if: github.event_name != 'pull_request'
env:
DIGEST: ${{ steps.build.outputs.digest }}
IMAGE_NAME: ${{ steps.meta.outputs.image_name }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
if [ -z "$DIGEST" ]; then
echo "No digest available, skipping signing"
exit 0
fi
echo "Signing skill artifact ${IMAGE_NAME}@${DIGEST}"
cosign sign --yes "${IMAGE_NAME}@${DIGEST}"
cosign sign --yes "${IMAGE_NAME}:${VERSION}"
echo "Signed: ${IMAGE_NAME}@${DIGEST} and ${IMAGE_NAME}:${VERSION}" >> $GITHUB_STEP_SUMMARY
- name: Check out skill source for SBOM
if: github.event_name != 'pull_request' && steps.build.outputs.digest != ''
id: skill-src
env:
SKILL_REPO: ${{ steps.meta.outputs.repository }}
SKILL_REF: ${{ steps.meta.outputs.ref }}
SKILL_PATH: ${{ steps.meta.outputs.skill_path }}
run: |
rm -rf /tmp/skill-src
mkdir -p /tmp/skill-src
git clone --filter=tree:0 --no-checkout "$SKILL_REPO" /tmp/skill-src/repo
git -C /tmp/skill-src/repo checkout "$SKILL_REF"
if [ -n "$SKILL_PATH" ]; then
src_dir="/tmp/skill-src/repo/$SKILL_PATH"
else
src_dir="/tmp/skill-src/repo"
fi
echo "source_dir=$src_dir" >> $GITHUB_OUTPUT
- name: Generate SBOM from skill source
if: github.event_name != 'pull_request' && steps.build.outputs.digest != ''
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: ${{ steps.skill-src.outputs.source_dir }}
format: spdx-json
output-file: /tmp/skill-sbom.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Attest SBOM for skill artifact
if: github.event_name != 'pull_request' && steps.build.outputs.digest != ''
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
with:
subject-name: ${{ steps.meta.outputs.image_name }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: /tmp/skill-sbom.spdx.json
push-to-registry: true
- name: Attest build provenance for skill artifact
if: github.event_name != 'pull_request' && steps.build.outputs.digest != ''
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ${{ steps.meta.outputs.image_name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Create security scan attestation (SCAI format)
if: github.event_name != 'pull_request' && steps.build.outputs.digest != ''
env:
DIGEST: ${{ steps.build.outputs.digest }}
IMAGE_NAME: ${{ steps.meta.outputs.image_name }}
CONFIG_FILE: ${{ matrix.config }}
COMMIT_SHA: ${{ github.sha }}
RUN_ID: ${{ github.run_id }}
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Prior-trust path: this skill was not in scan-configs for this run
# (e.g. go.mod-only push that rebuilds everything but only re-scans
# changed specs). The previous SCAI attestation on the registry
# remains authoritative; skipping here is safe and intentional.
if [ ! -f /tmp/skill-scan-results/scan-summary.json ]; then
echo "No fresh scan summary for this run; skipping SCAI attestation (skill not in this run's scan-configs)."
exit 0
fi
SCANNER_VERSION=""
if [ -f /tmp/skill-scan-results/scanner-version.txt ]; then
SCANNER_VERSION=$(tr -d '\n' < /tmp/skill-scan-results/scanner-version.txt)
echo "Scanner version: $SCANNER_VERSION"
fi
python3 scripts/skill-scan/generate_scai_attestation.py \
/tmp/skill-scan-results/scan-summary.json \
"$IMAGE_NAME" \
"$DIGEST" \
--config-file "$CONFIG_FILE" \
--commit-sha "$COMMIT_SHA" \
--run-id "$RUN_ID" \
--run-url "${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}" \
--producer-uri "${SERVER_URL}/${REPO}" \
--scanner-version "$SCANNER_VERSION" \
--scanner-uri "https://github.com/cisco-ai-defense/skill-scanner" \
--validate \
--output /tmp/skill-scai.json
echo "Generated SCAI attestation:"
cat /tmp/skill-scai.json
cosign attest --yes \
--predicate /tmp/skill-scai.json \
--type https://in-toto.io/attestation/scai/v0.3 \
"${IMAGE_NAME}@${DIGEST}"
ATTRIBUTE=$(python3 -c "import json,sys;print(json.load(open('/tmp/skill-scai.json'))['predicate']['attributes'][0]['attribute'])")
echo "SCAI security scan attestation created (${ATTRIBUTE})" >> $GITHUB_STEP_SUMMARY
rm -f /tmp/skill-scai.json
- name: Build summary
env:
SKILL_NAME: ${{ steps.meta.outputs.skill_name }}
VERSION: ${{ steps.meta.outputs.version }}
IMAGE_NAME: ${{ steps.meta.outputs.image_name }}
DIGEST: ${{ steps.build.outputs.digest }}
run: |
echo "## Skill Build: ${SKILL_NAME}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Skill | ${SKILL_NAME} |" >> $GITHUB_STEP_SUMMARY
echo "| Version | ${VERSION} |" >> $GITHUB_STEP_SUMMARY
echo "| Image | \`${IMAGE_NAME}:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
if [ -n "$DIGEST" ]; then
echo "| Digest | \`${DIGEST}\` |" >> $GITHUB_STEP_SUMMARY
fi
echo "| Status | ${{ github.event_name != 'pull_request' && 'Published' || 'Built (dry run)' }} |" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event_name }}" != "pull_request" ] && [ -n "$DIGEST" ]; then
echo "| SBOM | Attested (SPDX) |" >> $GITHUB_STEP_SUMMARY
echo "| Provenance | Attested (SLSA) |" >> $GITHUB_STEP_SUMMARY
fi
save-pr-number:
runs-on: ubuntu-latest
permissions: {}
if: github.event_name == 'pull_request'
steps:
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: echo "$PR_NUMBER" > pr-number.txt
- name: Upload PR number
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: skill-pr-number
path: pr-number.txt
retention-days: 5
summary:
needs: [discover-skill-configs, validate-skills, skill-security-scan, build-skill-artifacts]
runs-on: ubuntu-latest
if: always()
permissions:
contents: read
steps:
- name: Build summary
env:
CONFIGS: ${{ needs.discover-skill-configs.outputs.changed-configs }}
SCAN_CONFIGS: ${{ needs.discover-skill-configs.outputs.scan-configs }}
VALIDATE_RESULT: ${{ needs.validate-skills.result }}
SCAN_RESULT: ${{ needs.skill-security-scan.result }}
BUILD_RESULT: ${{ needs.build-skill-artifacts.result }}
run: |
echo "## Skill Build Pipeline Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Stage | Result |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Trigger | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY
echo "| Skills to build | $(echo "$CONFIGS" | jq '. | length') |" >> $GITHUB_STEP_SUMMARY
echo "| Skills to scan | $(echo "$SCAN_CONFIGS" | jq '. | length') |" >> $GITHUB_STEP_SUMMARY
echo "| Validation | ${VALIDATE_RESULT} |" >> $GITHUB_STEP_SUMMARY
echo "| Security scan | ${SCAN_RESULT} |" >> $GITHUB_STEP_SUMMARY
echo "| Build | ${BUILD_RESULT} |" >> $GITHUB_STEP_SUMMARY