From 09b845573180a7d5ba22cd36b2a2c734e98dd608 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Mar 2026 20:57:32 +0000
Subject: [PATCH 1/2] Initial plan
From 53bb57d5ed1705bfcac387b09d7a8d71c04e901f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:21:11 +0000
Subject: [PATCH 2/2] feat: add daily-integrity-analysis workflow and expose
filtered_integrity in MCP logs tool
- Add `filtered_integrity` parameter to MCP `logs` tool in mcp_tools_privileged.go
- Update agentic_workflows_guide.md to document the new `filtered_integrity` parameter
- Create .github/workflows/daily-integrity-analysis.md:
- Daily schedule with pre-step that downloads logs using --filtered-integrity flag
- Agent uses Python (pandas/matplotlib/seaborn) to bucketize DIFC events
- Generates 3 charts: timeline, top filtered tools, reason/tag breakdown
- Provides actionable integrity system tuning recommendations
- Publishes results as a GitHub discussion via create-discussion safe-output
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../daily-integrity-analysis.lock.yml | 1336 +++++++++++++++++
.github/workflows/daily-integrity-analysis.md | 434 ++++++
actions/setup/md/agentic_workflows_guide.md | 1 +
pkg/cli/mcp_tools_privileged.go | 32 +-
4 files changed, 1789 insertions(+), 14 deletions(-)
create mode 100644 .github/workflows/daily-integrity-analysis.lock.yml
create mode 100644 .github/workflows/daily-integrity-analysis.md
diff --git a/.github/workflows/daily-integrity-analysis.lock.yml b/.github/workflows/daily-integrity-analysis.lock.yml
new file mode 100644
index 00000000000..7159d2ef60d
--- /dev/null
+++ b/.github/workflows/daily-integrity-analysis.lock.yml
@@ -0,0 +1,1336 @@
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
+#
+# Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations
+#
+# Resolved workflow manifest:
+# Imports:
+# - shared/python-dataviz.md
+# - shared/reporting.md
+#
+# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"51a01527613f0564b51a32db3e875176da0073a411f18e86b898df02603357dd","strict":true}
+
+name: "Daily DIFC Integrity-Filtered Events Analyzer"
+"on":
+ schedule:
+ - cron: "19 8 * * *"
+ # Friendly format: daily (scattered)
+ workflow_dispatch:
+
+permissions: {}
+
+concurrency:
+ group: "gh-aw-${{ github.workflow }}"
+
+run-name: "Daily DIFC Integrity-Filtered Events Analyzer"
+
+jobs:
+ activation:
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Generate agentic run info
+ id: generate_aw_info
+ env:
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_INFO_VERSION: ""
+ GH_AW_INFO_AGENT_VERSION: "latest"
+ GH_AW_INFO_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_INFO_EXPERIMENTAL: "false"
+ GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
+ GH_AW_INFO_STAGED: "false"
+ GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python"]'
+ GH_AW_INFO_FIREWALL_ENABLED: "true"
+ GH_AW_INFO_AWF_VERSION: "v0.24.3"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_COMPILED_STRICT: "true"
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .github
+ .agents
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "daily-integrity-analysis.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
+ {
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/agentic_workflows_guide.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ Tools: create_discussion, upload_asset, missing_tool, missing_data, noop
+
+ upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs).
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ {{#runtime-import .github/workflows/shared/reporting.md}}
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ {{#runtime-import .github/workflows/shared/python-dataviz.md}}
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ {{#runtime-import .github/workflows/daily-integrity-analysis.md}}
+ GH_AW_PROMPT_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ALLOWED_EXTENSIONS: ''
+ GH_AW_CACHE_DESCRIPTION: ''
+ GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/'
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS,
+ GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION,
+ GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR,
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh
+ - name: Upload activation artifact
+ if: success()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: activation
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ discussions: read
+ issues: read
+ pull-requests: read
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg"
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_WORKFLOW_ID_SANITIZED: dailyintegrityanalysis
+ outputs:
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
+ model: ${{ needs.activation.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Set runtime paths
+ run: |
+ echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_ENV"
+ echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_ENV"
+ echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_ENV"
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Setup Go for CLI build
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
+ with:
+ go-version-file: go.mod
+ cache: true
+ - name: Build gh-aw CLI
+ run: |
+ echo "Building gh-aw CLI for linux/amd64..."
+ mkdir -p dist
+ VERSION=$(git describe --tags --always --dirty)
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
+ -ldflags "-s -w -X main.version=${VERSION}" \
+ -o dist/gh-aw-linux-amd64 \
+ ./cmd/gh-aw
+ # Copy binary to root for direct execution in user-defined steps
+ cp dist/gh-aw-linux-amd64 ./gh-aw
+ chmod +x ./gh-aw
+ echo "✓ Built gh-aw CLI successfully"
+ - name: Setup Docker Buildx
+ uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
+ - name: Build gh-aw Docker image
+ uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
+ with:
+ context: .
+ platforms: linux/amd64
+ push: false
+ load: true
+ tags: localhost/gh-aw:dev
+ build-args: |
+ BINARY=dist/gh-aw-linux-amd64
+ - name: Create gh-aw temp directory
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - name: Configure gh CLI for GitHub Enterprise
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh
+ env:
+ GH_TOKEN: ${{ github.token }}
+ - name: Setup Python environment
+ run: "# Create working directory for Python scripts\nmkdir -p /tmp/gh-aw/python\nmkdir -p /tmp/gh-aw/python/data\nmkdir -p /tmp/gh-aw/python/charts\nmkdir -p /tmp/gh-aw/python/artifacts\n\necho \"Python environment setup complete\"\necho \"Working directory: /tmp/gh-aw/python\"\necho \"Data directory: /tmp/gh-aw/python/data\"\necho \"Charts directory: /tmp/gh-aw/python/charts\"\necho \"Artifacts directory: /tmp/gh-aw/python/artifacts\"\n"
+ - name: Install Python scientific libraries
+ run: "pip install --user --quiet numpy pandas matplotlib seaborn scipy\n\n# Verify installations\npython3 -c \"import numpy; print(f'NumPy {numpy.__version__} installed')\"\npython3 -c \"import pandas; print(f'Pandas {pandas.__version__} installed')\"\npython3 -c \"import matplotlib; print(f'Matplotlib {matplotlib.__version__} installed')\"\npython3 -c \"import seaborn; print(f'Seaborn {seaborn.__version__} installed')\"\npython3 -c \"import scipy; print(f'SciPy {scipy.__version__} installed')\"\n\necho \"All scientific libraries installed successfully\"\n"
+ - if: always()
+ name: Upload charts
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ if-no-files-found: warn
+ name: data-charts
+ path: /tmp/gh-aw/python/charts/*.png
+ retention-days: 30
+ - if: always()
+ name: Upload source files and data
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ if-no-files-found: warn
+ name: python-source-and-data
+ path: |
+ /tmp/gh-aw/python/*.py
+ /tmp/gh-aw/python/data/*
+ retention-days: 30
+ - env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ name: Download integrity-filtered logs
+ run: "mkdir -p /tmp/gh-aw/integrity\n# Download logs filtered to only runs with DIFC integrity-filtered events\n./gh-aw logs --filtered-integrity --start-date -7d --json -c 200 \\\n > /tmp/gh-aw/integrity/filtered-logs.json\n\nif [ -f /tmp/gh-aw/integrity/filtered-logs.json ]; then\n count=$(jq '. | length' /tmp/gh-aw/integrity/filtered-logs.json 2>/dev/null || echo 0)\n echo \"✅ Downloaded $count runs with integrity-filtered events\"\nelse\n echo \"⚠️ No logs file produced; continuing with empty dataset\"\n echo \"[]\" > /tmp/gh-aw/integrity/filtered-logs.json\nfi\n"
+
+ # Cache memory file share configuration from frontmatter processed below
+ - name: Create cache-memory directory
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh
+ - name: Restore cache-memory file share data
+ uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+ with:
+ key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
+ path: /tmp/gh-aw/cache-memory
+ restore-keys: |
+ memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ (github.event.pull_request) || (github.event.issue.pull_request)
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest
+ env:
+ GH_HOST: github.com
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.24.3
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ with:
+ script: |
+ const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.24.3 ghcr.io/github/gh-aw-firewall/api-proxy:0.24.3 ghcr.io/github/gh-aw-firewall/squid:0.24.3 ghcr.io/github/gh-aw-mcpg:v0.1.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine
+ - name: Install gh-aw extension
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: |
+ # Check if gh-aw extension is already installed
+ if gh extension list | grep -q "github/gh-aw"; then
+ echo "gh-aw extension already installed, upgrading..."
+ gh extension upgrade gh-aw || true
+ else
+ echo "Installing gh-aw extension..."
+ gh extension install github/gh-aw
+ fi
+ gh aw --version
+ # Copy the gh-aw binary to ${RUNNER_TEMP}/gh-aw for MCP server containerization
+ mkdir -p ${RUNNER_TEMP}/gh-aw
+ GH_AW_BIN=$(which gh-aw 2>/dev/null || find ~/.local/share/gh/extensions/gh-aw -name 'gh-aw' -type f 2>/dev/null | head -1)
+ if [ -n "$GH_AW_BIN" ] && [ -f "$GH_AW_BIN" ]; then
+ cp "$GH_AW_BIN" ${RUNNER_TEMP}/gh-aw/gh-aw
+ chmod +x ${RUNNER_TEMP}/gh-aw/gh-aw
+ echo "Copied gh-aw binary to ${RUNNER_TEMP}/gh-aw/gh-aw"
+ else
+ echo "::error::Failed to find gh-aw binary for MCP server"
+ exit 1
+ fi
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
+ {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ - name: Write Safe Outputs Tools
+ run: |
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF'
+ {
+ "description_suffixes": {
+ "create_discussion": " CONSTRAINTS: Maximum 1 discussion(s) can be created. Title will be prefixed with \"[integrity] \". Discussions will be created in category \"audits\".",
+ "upload_asset": " CONSTRAINTS: Maximum file size: 10240KB. Allowed file extensions: [.png .jpg .jpeg]."
+ },
+ "repo_params": {},
+ "dynamic_tools": []
+ }
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
+ {
+ "create_discussion": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "category": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ },
+ "title": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ },
+ "upload_asset": {
+ "defaultMax": 10,
+ "fields": {
+ "path": {
+ "required": true,
+ "type": "string"
+ }
+ }
+ }
+ }
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
+ node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_ASSETS_ALLOWED_EXTS: ${{ env.GH_AW_ASSETS_ALLOWED_EXTS }}
+ GH_AW_ASSETS_BRANCH: ${{ env.GH_AW_ASSETS_BRANCH }}
+ GH_AW_ASSETS_MAX_SIZE_KB: ${{ env.GH_AW_ASSETS_MAX_SIZE_KB }}
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
+ GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.19'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "agenticworkflows": {
+ "type": "stdio",
+ "container": "localhost/gh-aw:dev",
+ "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"],
+ "args": ["--network", "host", "-w", "\${GITHUB_WORKSPACE}"],
+ "env": {
+ "DEBUG": "*",
+ "GITHUB_TOKEN": "\${GITHUB_TOKEN}",
+ "GITHUB_ACTOR": "\${GITHUB_ACTOR}",
+ "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}"
+ },
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ]
+ }
+ }
+ },
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.32.0",
+ "env": {
+ "GITHUB_HOST": "\${GITHUB_SERVER_URL}",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
+ },
+ "guard-policies": {
+ "allow-only": {
+ "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
+ "repos": "$GITHUB_MCP_GUARD_REPOS"
+ }
+ }
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ },
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ]
+ }
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ GH_AW_MCP_CONFIG_EOF
+ - name: Download activation artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
+ - name: Clean git credentials
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.3 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg"
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_PHASE: agent
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_VERSION: dev
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Detect inference access error
+ id: detect-inference-error
+ if: always()
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP Gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Append agent step summary
+ if: always()
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh
+ - name: Copy Safe Outputs
+ if: always()
+ run: |
+ mkdir -p /tmp/gh-aw
+ cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
+ - name: Ingest agent output
+ id: collect_output
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
+ - name: Upload cache-memory data as artifact
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ if: always()
+ with:
+ name: cache-memory
+ path: /tmp/gh-aw/cache-memory
+ # Upload safe-outputs assets for upload_assets job
+ - name: Upload Safe Outputs Assets
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: safe-outputs-assets
+ path: /tmp/gh-aw/safeoutputs/assets/
+ retention-days: 1
+ if-no-files-found: ignore
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: agent
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/agent/
+ /tmp/gh-aw/safeoutputs.jsonl
+ /tmp/gh-aw/agent_output.json
+ if-no-files-found: ignore
+ # --- Threat Detection (inline) ---
+ - name: Check if detection needed
+ id: detection_guard
+ if: always()
+ env:
+ OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ run: |
+ if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
+ echo "run_detection=true" >> "$GITHUB_OUTPUT"
+ echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
+ else
+ echo "run_detection=false" >> "$GITHUB_OUTPUT"
+ echo "Detection skipped: no agent outputs or patches to analyze"
+ fi
+ - name: Clear MCP configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.copilot/mcp-config.json
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ done
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ - name: Setup threat detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ WORKFLOW_DESCRIPTION: "Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations"
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.3 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PHASE: detection
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_VERSION: dev
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_detection_results
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+ - name: Set detection conclusion
+ id: detection_conclusion
+ if: always()
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
+ run: |
+ if [[ "$RUN_DETECTION" != "true" ]]; then
+ echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection was not needed, marking as skipped"
+ elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
+ echo "conclusion=success" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection passed successfully"
+ else
+ echo "conclusion=failure" >> "$GITHUB_OUTPUT"
+ echo "success=false" >> "$GITHUB_OUTPUT"
+ echo "Detection found issues"
+ fi
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - safe_outputs
+ - update_cache_memory
+ - upload_assets
+ if: (always()) && ((needs.agent.result != 'skipped') || (needs.activation.outputs.lockdown_check_failed == 'true'))
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ discussions: write
+ issues: write
+ concurrency:
+ group: "gh-aw-conclusion-daily-integrity-analysis"
+ cancel-in-progress: false
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "daily-integrity-analysis"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ GH_AW_CREATE_DISCUSSION_ERRORS: ${{ needs.safe_outputs.outputs.create_discussion_errors }}
+ GH_AW_CREATE_DISCUSSION_ERROR_COUNT: ${{ needs.safe_outputs.outputs.create_discussion_error_count }}
+ GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
+ GH_AW_GROUP_REPORTS: "false"
+ GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
+ GH_AW_TIMEOUT_MINUTES: "30"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Handle No-Op Message
+ id: handle_noop_message
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+
+ safe_outputs:
+ needs: agent
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ discussions: write
+ issues: write
+ timeout-minutes: 15
+ env:
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-integrity-analysis"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ outputs:
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV"
+ - name: Configure GH_HOST for enterprise compatibility
+ shell: bash
+ run: |
+ # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
+ # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
+ GH_HOST="${GITHUB_SERVER_URL#https://}"
+ GH_HOST="${GH_HOST#http://}"
+ echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[integrity] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\"],\"branch\":\"assets/${{ github.workflow }}\",\"max-size\":10240}}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Upload safe output items
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: safe-output-items
+ path: /tmp/gh-aw/safe-output-items.jsonl
+ if-no-files-found: ignore
+
+ update_cache_memory:
+ needs: agent
+ if: always() && needs.agent.outputs.detection_success == 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ env:
+ GH_AW_WORKFLOW_ID_SANITIZED: dailyintegrityanalysis
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download cache-memory artifact (default)
+ id: download_cache_default
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ continue-on-error: true
+ with:
+ name: cache-memory
+ path: /tmp/gh-aw/cache-memory
+ - name: Check if cache-memory folder has content (default)
+ id: check_cache_default
+ shell: bash
+ run: |
+ if [ -d "/tmp/gh-aw/cache-memory" ] && [ "$(ls -A /tmp/gh-aw/cache-memory 2>/dev/null)" ]; then
+ echo "has_content=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "has_content=false" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Save cache-memory to cache (default)
+ if: steps.check_cache_default.outputs.has_content == 'true'
+ uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+ with:
+ key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
+ path: /tmp/gh-aw/cache-memory
+
+ upload_assets:
+ needs: agent
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'upload_asset'))
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ timeout-minutes: 10
+ outputs:
+ branch_name: ${{ steps.upload_assets.outputs.branch_name }}
+ published_count: ${{ steps.upload_assets.outputs.published_count }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Download assets
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: safe-outputs-assets
+ path: /tmp/gh-aw/safeoutputs/assets/
+ - name: List downloaded asset files
+ continue-on-error: true
+ run: |
+ echo "Downloaded asset files:"
+ find /tmp/gh-aw/safeoutputs/assets/ -maxdepth 1 -ls
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV"
+ - name: Push assets
+ id: upload_assets
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg"
+ GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_ENGINE_ID: "copilot"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/upload_assets.cjs');
+ await main();
+
diff --git a/.github/workflows/daily-integrity-analysis.md b/.github/workflows/daily-integrity-analysis.md
new file mode 100644
index 00000000000..75dd2c9cb9b
--- /dev/null
+++ b/.github/workflows/daily-integrity-analysis.md
@@ -0,0 +1,434 @@
+---
+description: Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations
+on:
+ schedule:
+ - cron: daily
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ actions: read
+ issues: read
+ pull-requests: read
+ discussions: read
+
+tracker-id: daily-integrity-analysis
+engine: copilot
+
+steps:
+ - name: Download integrity-filtered logs
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ mkdir -p /tmp/gh-aw/integrity
+ # Download logs filtered to only runs with DIFC integrity-filtered events
+ ./gh-aw logs --filtered-integrity --start-date -7d --json -c 200 \
+ > /tmp/gh-aw/integrity/filtered-logs.json
+
+ if [ -f /tmp/gh-aw/integrity/filtered-logs.json ]; then
+ count=$(jq '. | length' /tmp/gh-aw/integrity/filtered-logs.json 2>/dev/null || echo 0)
+ echo "✅ Downloaded $count runs with integrity-filtered events"
+ else
+ echo "⚠️ No logs file produced; continuing with empty dataset"
+ echo "[]" > /tmp/gh-aw/integrity/filtered-logs.json
+ fi
+
+tools:
+ agentic-workflows:
+ bash:
+ - "*"
+
+safe-outputs:
+ upload-asset:
+ create-discussion:
+ expires: 3d
+ category: "audits"
+ title-prefix: "[integrity] "
+ max: 1
+ close-older-discussions: true
+
+timeout-minutes: 30
+
+imports:
+ - shared/reporting.md
+ - shared/python-dataviz.md
+---
+
+{{#runtime-import? .github/shared-instructions.md}}
+
+# Daily DIFC Integrity-Filtered Events Analyzer
+
+You are an integrity-system analyst. Your job is to analyze DIFC (Data Integrity and Flow Control) filtered events collected from agentic workflow runs, produce statistical charts that reveal patterns, and provide actionable tuning recommendations.
+
+## Context
+
+- **Repository**: ${{ github.repository }}
+- **Run ID**: ${{ github.run_id }}
+- **Data file**: `/tmp/gh-aw/integrity/filtered-logs.json` (pre-downloaded runs with DIFC integrity-filtered events)
+- **Analysis window**: Last 7 days
+
+## Step 0: Check for Data
+
+Read `/tmp/gh-aw/integrity/filtered-logs.json`. If the array is empty (no runs found in the last 7 days), call `noop` with the message "No DIFC integrity-filtered events found in the last 7 days." and stop.
+
+## Step 1: Parse and Bucketize Events
+
+The JSON file is an array of workflow run objects. Each run object contains `databaseId` (the numeric run ID) and `workflowName`. Use the `audit` tool from the agentic-workflows MCP server to get the detailed gateway data for each run. Then write a Python script to bucketize events.
+
+### 1.1 Fetch Detailed Gateway Data
+
+1. Read `/tmp/gh-aw/integrity/filtered-logs.json` and extract all run IDs by iterating over the array and collecting each entry's `databaseId` field.
+2. For each run ID, call the `audit` tool to get its detailed DIFC filtered events:
+
+```json
+{
+ "run_id": 12345678
+}
+```
+
+The audit result contains `gateway_analysis.filtered_events[]` with fields:
+- `timestamp` — ISO 8601 timestamp
+- `server_id` — MCP server that was filtered
+- `tool_name` — tool call that was blocked
+- `reason` — reason for filtering (e.g., `integrity`, `secrecy`)
+- `integrity_tags` — integrity labels applied
+- `secrecy_tags` — secrecy labels applied
+- `author_association` — contributor association of the triggering actor
+- `author_login` — login of the triggering actor
+
+3. For each event returned, annotate it with two additional fields from the corresponding run entry in `filtered-logs.json`: `workflow_name` (from `workflowName`) and `run_id` (from `databaseId`). This allows the Python analysis to group events by workflow.
+4. Collect all annotated filtered events across all runs and save them to `/tmp/gh-aw/integrity/all-events.json`.
+
+### 1.2 Python Bucketization Script
+
+Create and run `/tmp/gh-aw/integrity/bucketize.py`:
+
+```python
+#!/usr/bin/env python3
+"""Bucketize and centralize DIFC integrity-filtered events for statistical analysis."""
+import json
+import os
+from collections import defaultdict, Counter
+from datetime import datetime, timedelta
+
+DATA_DIR = "/tmp/gh-aw/integrity"
+os.makedirs(DATA_DIR, exist_ok=True)
+
+# Load all events
+with open(f"{DATA_DIR}/all-events.json") as f:
+ events = json.load(f)
+
+if not events:
+ print("No events to analyze.")
+ summary = {"total": 0, "by_tool": {}, "by_server": {}, "by_reason": {}, "by_hour": {}, "by_day": {}, "by_workflow": {}}
+ with open(f"{DATA_DIR}/summary.json", "w") as f:
+ json.dump(summary, f, indent=2)
+ exit(0)
+
+# Parse timestamps
+for e in events:
+ try:
+ e["_dt"] = datetime.fromisoformat(e["timestamp"].replace("Z", "+00:00"))
+ except Exception:
+ e["_dt"] = None
+
+# Buckets
+by_tool = Counter(e["tool_name"] for e in events if e.get("tool_name"))
+by_server = Counter(e["server_id"] for e in events if e.get("server_id"))
+by_reason = Counter(e["reason"] for e in events if e.get("reason"))
+by_workflow = Counter(e.get("workflow_name", "unknown") for e in events)
+
+# Time-based buckets
+by_hour = Counter()
+by_day = Counter()
+for e in events:
+ if e["_dt"]:
+ by_hour[e["_dt"].strftime("%Y-%m-%dT%H:00")] += 1
+ by_day[e["_dt"].strftime("%Y-%m-%d")] += 1
+
+# Integrity tag breakdown
+all_integrity_tags = Counter()
+all_secrecy_tags = Counter()
+for e in events:
+ for tag in (e.get("integrity_tags") or []):
+ all_integrity_tags[tag] += 1
+ for tag in (e.get("secrecy_tags") or []):
+ all_secrecy_tags[tag] += 1
+
+summary = {
+ "total": len(events),
+ "by_tool": dict(by_tool.most_common()),
+ "by_server": dict(by_server.most_common()),
+ "by_reason": dict(by_reason.most_common()),
+ "by_workflow": dict(by_workflow.most_common()),
+ "by_hour": dict(sorted(by_hour.items())),
+ "by_day": dict(sorted(by_day.items())),
+ "integrity_tags": dict(all_integrity_tags.most_common()),
+ "secrecy_tags": dict(all_secrecy_tags.most_common()),
+}
+
+with open(f"{DATA_DIR}/summary.json", "w") as f:
+ json.dump(summary, f, indent=2)
+
+print(f"Bucketized {len(events)} events.")
+print(json.dumps(summary, indent=2))
+```
+
+Run the script: `python3 /tmp/gh-aw/integrity/bucketize.py`
+
+## Step 2: Generate Statistical Charts
+
+Create and run chart scripts using matplotlib/seaborn. Save all charts to `/tmp/gh-aw/integrity/charts/`.
+
+```bash
+mkdir -p /tmp/gh-aw/integrity/charts
+```
+
+### Chart 1: Events Over Time (Daily)
+
+Create `/tmp/gh-aw/integrity/chart_timeline.py`:
+
+```python
+#!/usr/bin/env python3
+"""Chart 1: DIFC filtered events per day."""
+import json, os
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+import seaborn as sns
+from datetime import datetime
+
+DATA_DIR = "/tmp/gh-aw/integrity"
+CHARTS_DIR = f"{DATA_DIR}/charts"
+os.makedirs(CHARTS_DIR, exist_ok=True)
+
+with open(f"{DATA_DIR}/summary.json") as f:
+ summary = json.load(f)
+
+by_day = summary.get("by_day", {})
+if not by_day:
+ print("No daily data; skipping chart 1.")
+ exit(0)
+
+dates = [datetime.strptime(d, "%Y-%m-%d") for d in sorted(by_day)]
+counts = [by_day[d.strftime("%Y-%m-%d")] for d in dates]
+
+sns.set_style("whitegrid")
+fig, ax = plt.subplots(figsize=(12, 5), dpi=300)
+ax.bar(dates, counts, color="#4A90D9", edgecolor="white", linewidth=0.8)
+ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d"))
+ax.xaxis.set_major_locator(mdates.DayLocator())
+plt.xticks(rotation=45, ha="right")
+ax.set_title("DIFC Integrity-Filtered Events — Last 7 Days", fontsize=16, fontweight="bold", pad=14)
+ax.set_xlabel("Date", fontsize=13)
+ax.set_ylabel("Event Count", fontsize=13)
+ax.grid(True, axis="y", alpha=0.4)
+plt.tight_layout()
+plt.savefig(f"{CHARTS_DIR}/events_timeline.png", dpi=300, bbox_inches="tight", facecolor="white")
+print("Chart 1 saved.")
+```
+
+Run: `python3 /tmp/gh-aw/integrity/chart_timeline.py`
+
+### Chart 2: Top Filtered Tools (Horizontal Bar)
+
+Create `/tmp/gh-aw/integrity/chart_tools.py`:
+
+```python
+#!/usr/bin/env python3
+"""Chart 2: Top tools that trigger DIFC filtering."""
+import json, os
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+DATA_DIR = "/tmp/gh-aw/integrity"
+CHARTS_DIR = f"{DATA_DIR}/charts"
+os.makedirs(CHARTS_DIR, exist_ok=True)
+
+with open(f"{DATA_DIR}/summary.json") as f:
+ summary = json.load(f)
+
+by_tool = summary.get("by_tool", {})
+if not by_tool:
+ print("No tool data; skipping chart 2.")
+ exit(0)
+
+items = sorted(by_tool.items(), key=lambda x: x[1], reverse=True)[:15]
+tools = [i[0] for i in items]
+counts = [i[1] for i in items]
+
+sns.set_style("whitegrid")
+fig, ax = plt.subplots(figsize=(12, max(5, len(tools) * 0.55)), dpi=300)
+bars = ax.barh(tools[::-1], counts[::-1], color="#E8714A", edgecolor="white", linewidth=0.8)
+for bar, val in zip(bars, counts[::-1]):
+ ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height() / 2,
+ str(val), va="center", fontsize=11, fontweight="bold")
+ax.set_title("Top Filtered Tool Calls (DIFC)", fontsize=16, fontweight="bold", pad=14)
+ax.set_xlabel("Event Count", fontsize=13)
+ax.set_ylabel("Tool Name", fontsize=13)
+ax.grid(True, axis="x", alpha=0.4)
+plt.tight_layout()
+plt.savefig(f"{CHARTS_DIR}/top_tools.png", dpi=300, bbox_inches="tight", facecolor="white")
+print("Chart 2 saved.")
+```
+
+Run: `python3 /tmp/gh-aw/integrity/chart_tools.py`
+
+### Chart 3: Filter Reason Breakdown (Pie / Donut)
+
+Create `/tmp/gh-aw/integrity/chart_reasons.py`:
+
+```python
+#!/usr/bin/env python3
+"""Chart 3: Breakdown of filter reasons and integrity/secrecy tags."""
+import json, os
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+DATA_DIR = "/tmp/gh-aw/integrity"
+CHARTS_DIR = f"{DATA_DIR}/charts"
+os.makedirs(CHARTS_DIR, exist_ok=True)
+
+with open(f"{DATA_DIR}/summary.json") as f:
+ summary = json.load(f)
+
+by_reason = summary.get("by_reason", {})
+integrity_tags = summary.get("integrity_tags", {})
+secrecy_tags = summary.get("secrecy_tags", {})
+
+sns.set_style("whitegrid")
+fig, axes = plt.subplots(1, 2, figsize=(14, 6), dpi=300)
+
+# Left: filter reasons pie
+if by_reason:
+ labels = list(by_reason.keys())
+ values = list(by_reason.values())
+ colors = sns.color_palette("husl", len(labels))
+ axes[0].pie(values, labels=labels, colors=colors, autopct="%1.1f%%",
+ startangle=140, pctdistance=0.82,
+ wedgeprops=dict(width=0.6))
+ axes[0].set_title("Filter Reason Distribution", fontsize=14, fontweight="bold")
+else:
+ axes[0].text(0.5, 0.5, "No reason data", ha="center", va="center")
+ axes[0].set_title("Filter Reason Distribution", fontsize=14, fontweight="bold")
+
+# Right: top integrity/secrecy tags bar
+all_tags = {**{f"[I] {k}": v for k, v in integrity_tags.items()},
+ **{f"[S] {k}": v for k, v in secrecy_tags.items()}}
+if all_tags:
+ tag_items = sorted(all_tags.items(), key=lambda x: x[1], reverse=True)[:10]
+ tag_names = [i[0] for i in tag_items]
+ tag_counts = [i[1] for i in tag_items]
+ colors2 = ["#4A90D9" if t.startswith("[I]") else "#E8714A" for t in tag_names]
+ axes[1].barh(tag_names[::-1], tag_counts[::-1], color=colors2[::-1], edgecolor="white")
+ axes[1].set_title("Top Integrity [I] & Secrecy [S] Tags", fontsize=14, fontweight="bold")
+ axes[1].set_xlabel("Count", fontsize=12)
+ axes[1].grid(True, axis="x", alpha=0.4)
+else:
+ axes[1].text(0.5, 0.5, "No tag data", ha="center", va="center")
+ axes[1].set_title("Top Integrity & Secrecy Tags", fontsize=14, fontweight="bold")
+
+fig.suptitle("DIFC Filter Analysis — Reason & Tag Breakdown", fontsize=16, fontweight="bold", y=1.01)
+plt.tight_layout()
+plt.savefig(f"{CHARTS_DIR}/reasons_tags.png", dpi=300, bbox_inches="tight", facecolor="white")
+print("Chart 3 saved.")
+```
+
+Run: `python3 /tmp/gh-aw/integrity/chart_reasons.py`
+
+## Step 3: Upload Charts
+
+Upload each generated chart using the `upload asset` tool and collect the returned URLs:
+1. Upload `/tmp/gh-aw/integrity/charts/events_timeline.png`
+2. Upload `/tmp/gh-aw/integrity/charts/top_tools.png`
+3. Upload `/tmp/gh-aw/integrity/charts/reasons_tags.png`
+
+## Step 4: Generate Tuning Recommendations
+
+Based on the summary data, derive actionable recommendations. For each top filtered tool or server:
+- Is the tool legitimately called on untrusted data? If yes, recommend applying integrity tags.
+- Are secrecy-tagged artifacts being passed to tools that don't need them? Recommend narrowing tool access.
+- Is the filter rate increasing? Recommend reviewing recent prompt changes.
+- Are there bursts (many events in one hour)? Investigate the workflow(s) involved.
+
+## Step 5: Create Discussion Report
+
+Create a GitHub discussion with the full analysis.
+
+**Title**: `[integrity] DIFC Integrity-Filtered Events Report — YYYY-MM-DD`
+
+**Body** (use h3 and lower for all headers per reporting guidelines):
+
+```markdown
+### Executive Summary
+
+In the last 7 days, **[N]** DIFC integrity-filtered events were detected across **[W]** workflow runs. The most frequently filtered tool was **[tool_name]** ([X] events), and the dominant filter reason was **[reason]**. [Describe the overall trend: stable/increasing/decreasing. Highlight any notable spike or pattern that warrants attention.]
+
+[Describe which workflows or MCP servers contributed the most events and what that suggests about how the integrity system is being exercised. If the filtering rate is high for a particular server, explain whether this is expected or a sign of misconfiguration.]
+
+### Key Metrics
+
+| Metric | Value |
+|--------|-------|
+| Total filtered events | [N] |
+| Unique tools filtered | [N] |
+| Unique workflows affected | [N] |
+| Most common filter reason | [reason] |
+| Busiest day | [YYYY-MM-DD] ([N] events) |
+
+### 📈 Events Over Time
+
+
+
+[2–3 sentence analysis: is there a trend? any spikes?]
+
+### 🔧 Top Filtered Tools
+
+
+
+[Brief analysis: which tools trigger the most filtering and why]
+
+### 🏷️ Filter Reasons and Tags
+
+
+
+[Analysis of integrity vs. secrecy filtering and top tags]
+
+
+📋 Per-Workflow Breakdown
+
+| Workflow | Filtered Events |
+|----------|----------------|
+[one row per workflow sorted by count descending]
+
+
+
+
+📋 Per-Server Breakdown
+
+| MCP Server | Filtered Events |
+|------------|----------------|
+[one row per server sorted by count descending]
+
+
+
+### 💡 Tuning Recommendations
+
+[Numbered list of specific, actionable recommendations derived from the analysis. Examples:
+- Tools appearing in top filtered: consider whether they need access to integrity-tagged data
+- High secrecy filter rate: review which workflows pass secrets to tools
+- Increasing trend: monitor and review recent prompt or tool permission changes
+- Workflow-specific spikes: examine the triggering events for that workflow]
+
+---
+*Generated by the Daily Integrity Analysis workflow*
+*Analysis window: Last 7 days | Repository: ${{ github.repository }}*
+*Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}*
+```
+
+## Important
+
+**Always** call a safe-output tool at the end of your run. If no events were found, call `noop`:
+
+```json
+{"noop": {"message": "No DIFC integrity-filtered events found in the last 7 days; no report generated."}}
+```
diff --git a/actions/setup/md/agentic_workflows_guide.md b/actions/setup/md/agentic_workflows_guide.md
index d573b55854e..5be95663512 100644
--- a/actions/setup/md/agentic_workflows_guide.md
+++ b/actions/setup/md/agentic_workflows_guide.md
@@ -22,6 +22,7 @@ Call all operations as MCP tools with JSON parameters.
- `engine`: filter by AI engine (`copilot`, `claude`, `codex`)
- `branch`: filter by branch name
- `firewall` / `no_firewall`: filter by firewall status
+- `filtered_integrity`: filter to only runs with DIFC integrity-filtered events in gateway logs
- `after_run_id` / `before_run_id`: paginate by run database ID
- Logs are saved to `/tmp/gh-aw/aw-mcp/logs/`
diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go
index 283b07928cb..d0ac85b8e1f 100644
--- a/pkg/cli/mcp_tools_privileged.go
+++ b/pkg/cli/mcp_tools_privileged.go
@@ -16,18 +16,19 @@ import (
// Returns an error if schema generation fails.
func registerLogsTool(server *mcp.Server, execCmd execCmdFunc, actor string, validateActor bool) error {
type logsArgs struct {
- WorkflowName string `json:"workflow_name,omitempty" jsonschema:"Name of the workflow to download logs for (empty for all)"`
- Count int `json:"count,omitempty" jsonschema:"Number of workflow runs to download (default: 100)"`
- StartDate string `json:"start_date,omitempty" jsonschema:"Filter runs created after this date (YYYY-MM-DD or delta like -1d, -1w, -1mo)"`
- EndDate string `json:"end_date,omitempty" jsonschema:"Filter runs created before this date (YYYY-MM-DD or delta like -1d, -1w, -1mo)"`
- Engine string `json:"engine,omitempty" jsonschema:"Filter logs by agentic engine type (claude, codex, copilot)"`
- Firewall bool `json:"firewall,omitempty" jsonschema:"Filter to only runs with firewall enabled"`
- NoFirewall bool `json:"no_firewall,omitempty" jsonschema:"Filter to only runs without firewall enabled"`
- Branch string `json:"branch,omitempty" jsonschema:"Filter runs by branch name"`
- AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"`
- BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"`
- Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in seconds to spend downloading logs (default: 50 for MCP server)"`
- MaxTokens int `json:"max_tokens,omitempty" jsonschema:"Maximum number of tokens in output before triggering guardrail (default: 12000)"`
+ WorkflowName string `json:"workflow_name,omitempty" jsonschema:"Name of the workflow to download logs for (empty for all)"`
+ Count int `json:"count,omitempty" jsonschema:"Number of workflow runs to download (default: 100)"`
+ StartDate string `json:"start_date,omitempty" jsonschema:"Filter runs created after this date (YYYY-MM-DD or delta like -1d, -1w, -1mo)"`
+ EndDate string `json:"end_date,omitempty" jsonschema:"Filter runs created before this date (YYYY-MM-DD or delta like -1d, -1w, -1mo)"`
+ Engine string `json:"engine,omitempty" jsonschema:"Filter logs by agentic engine type (claude, codex, copilot)"`
+ Firewall bool `json:"firewall,omitempty" jsonschema:"Filter to only runs with firewall enabled"`
+ NoFirewall bool `json:"no_firewall,omitempty" jsonschema:"Filter to only runs without firewall enabled"`
+ FilteredIntegrity bool `json:"filtered_integrity,omitempty" jsonschema:"Filter to only runs that contain DIFC integrity-filtered events in gateway logs"`
+ Branch string `json:"branch,omitempty" jsonschema:"Filter runs by branch name"`
+ AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"`
+ BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"`
+ Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in seconds to spend downloading logs (default: 50 for MCP server)"`
+ MaxTokens int `json:"max_tokens,omitempty" jsonschema:"Maximum number of tokens in output before triggering guardrail (default: 12000)"`
}
// Generate schema with elicitation defaults
@@ -120,6 +121,9 @@ return a schema description instead of the full output. Adjust the 'max_tokens'
if args.NoFirewall {
cmdArgs = append(cmdArgs, "--no-firewall")
}
+ if args.FilteredIntegrity {
+ cmdArgs = append(cmdArgs, "--filtered-integrity")
+ }
if args.Branch != "" {
cmdArgs = append(cmdArgs, "--branch", args.Branch)
}
@@ -141,8 +145,8 @@ return a schema description instead of the full output. Adjust the 'max_tokens'
cmdArgs = append(cmdArgs, "--json")
// Log the command being executed for debugging
- mcpLog.Printf("Executing logs tool: workflow=%s, count=%d, firewall=%v, no_firewall=%v, timeout=%d, command_args=%v",
- args.WorkflowName, args.Count, args.Firewall, args.NoFirewall, timeoutValue, cmdArgs)
+ mcpLog.Printf("Executing logs tool: workflow=%s, count=%d, firewall=%v, no_firewall=%v, filtered_integrity=%v, timeout=%d, command_args=%v",
+ args.WorkflowName, args.Count, args.Firewall, args.NoFirewall, args.FilteredIntegrity, timeoutValue, cmdArgs)
// Execute the CLI command
// Use separate stdout/stderr capture instead of CombinedOutput because: