Skip to content

feat(dotnet): add ASP.NET Core middleware example #2971

feat(dotnet): add ASP.NET Core middleware example

feat(dotnet): add ASP.NET Core middleware example #2971

Workflow file for this run

name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: "0 6 * * *" # Daily at 06:00 UTC
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# ── Path detection — determines which jobs to run ─────────────────────
changes:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
dotnet: ${{ steps.filter.outputs.dotnet }}
typescript: ${{ steps.filter.outputs.typescript }}
integrations: ${{ steps.filter.outputs.integrations }}
rust: ${{ steps.filter.outputs.rust }}
go: ${{ steps.filter.outputs.go }}
workflows: ${{ steps.filter.outputs.workflows }}
docs-only: ${{ steps.filter.outputs.docs-only }}
docker: ${{ steps.filter.outputs.docker }}
changed-py-pkgs: ${{ steps.py-pkgs.outputs.list }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
python:
- 'agent-governance-python/agent-os/**'
- 'agent-governance-python/agent-mesh/**'
- 'agent-governance-python/agent-hypervisor/**'
- 'agent-governance-python/agent-sre/**'
- 'agent-governance-python/agent-compliance/**'
- 'agent-governance-python/agent-runtime/**'
- 'agent-governance-python/agent-lightning/**'
- 'agent-governance-python/agent-primitives/**'
- 'agent-governance-python/agent-mcp-governance/**'
- 'agent-governance-python/agent-marketplace/**'
- 'scripts/**'
- 'agent-governance-python/requirements/**'
dotnet:
- 'agent-governance-dotnet/**'
typescript:
- 'agent-governance-typescript/**'
- 'agent-governance-python/agent-os/extensions/**'
- 'agent-governance-python/agentmesh-integrations/mastra-agentmesh/**'
- 'agent-governance-python/agentmesh-integrations/copilot-governance/**'
integrations:
- 'agent-governance-python/agentmesh-integrations/**'
workflows:
- '.github/workflows/**'
rust:
- 'agent-governance-rust/**'
go:
- 'agent-governance-golang/**'
docs-only:
- '**/*.md'
- 'agent-governance-python/notebooks/**'
- 'docs/**'
docker:
- 'Dockerfile'
- 'docker-compose*.yml'
- 'scripts/docker/**'
- '.gitattributes'
- 'agent-governance-python/**/pyproject.toml'
- 'agent-governance-python/agent-hypervisor/examples/dashboard/requirements.txt'
- '.github/workflows/ci.yml'
pkg-agent-os:
- 'agent-governance-python/agent-os/**'
pkg-agent-mesh:
- 'agent-governance-python/agent-mesh/**'
pkg-agent-hypervisor:
- 'agent-governance-python/agent-hypervisor/**'
pkg-agent-sre:
- 'agent-governance-python/agent-sre/**'
pkg-agent-compliance:
- 'agent-governance-python/agent-compliance/**'
pkg-agent-runtime:
- 'agent-governance-python/agent-runtime/**'
pkg-agent-lightning:
- 'agent-governance-python/agent-lightning/**'
- name: Compose changed-py-pkgs JSON list
id: py-pkgs
env:
AO: ${{ steps.filter.outputs.pkg-agent-os }}
AM: ${{ steps.filter.outputs.pkg-agent-mesh }}
AH: ${{ steps.filter.outputs.pkg-agent-hypervisor }}
AS: ${{ steps.filter.outputs.pkg-agent-sre }}
AC: ${{ steps.filter.outputs.pkg-agent-compliance }}
AR: ${{ steps.filter.outputs.pkg-agent-runtime }}
AL: ${{ steps.filter.outputs.pkg-agent-lightning }}
run: |
set -euo pipefail
pkgs=()
[ "$AO" = "true" ] && pkgs+=('"agent-os"')
[ "$AM" = "true" ] && pkgs+=('"agent-mesh"')
[ "$AH" = "true" ] && pkgs+=('"agent-hypervisor"')
[ "$AS" = "true" ] && pkgs+=('"agent-sre"')
[ "$AC" = "true" ] && pkgs+=('"agent-compliance"')
[ "$AR" = "true" ] && pkgs+=('"agent-runtime"')
[ "$AL" = "true" ] && pkgs+=('"agent-lightning"')
ifs=$IFS; IFS=,; list="[${pkgs[*]:-}]"; IFS=$ifs
echo "list=$list" >> "$GITHUB_OUTPUT"
echo "Changed Python packages: $list"
# ── Python lint + test (only when Python files change) ────────────────
lint:
needs: changes
if: needs.changes.outputs.python == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
strategy:
matrix:
package:
[
agent-os,
agent-mesh,
agent-hypervisor,
agent-sre,
agent-compliance,
agent-runtime,
agent-lightning,
]
steps:
- name: Determine if package changed
id: gate
env:
CHANGED: ${{ needs.changes.outputs.changed-py-pkgs }}
PKG: ${{ matrix.package }}
EVENT: ${{ github.event_name }}
run: |
set -euo pipefail
if [ "$EVENT" = "schedule" ] || [ "$EVENT" = "push" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
elif printf '%s' "$CHANGED" | grep -q "\"$PKG\""; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice::Package $PKG unchanged on this PR — skipping lint."
fi
- if: steps.gate.outputs.run == 'true'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- if: steps.gate.outputs.run == 'true'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- if: steps.gate.outputs.run == 'true'
name: Install ruff
run: pip install --require-hashes --no-cache-dir -r agent-governance-python/requirements/ci-lint.txt
- if: steps.gate.outputs.run == 'true'
name: Lint ${{ matrix.package }}
working-directory: agent-governance-python/${{ matrix.package }}
run: ruff check src/ --select E,F,W --ignore E501
# ── Python test (only when Python files change) ───────────────────────
test:
needs: changes
# Always run so matrix check names are reported to branch protection.
# Steps are skipped when no Python files changed.
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package:
[
agent-os,
agent-mesh,
agent-hypervisor,
agent-sre,
agent-compliance,
agent-runtime,
agent-lightning,
]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
exclude:
- package: agent-os
python-version: "3.10"
- package: agent-mesh
python-version: "3.10"
- package: agent-hypervisor
python-version: "3.10"
- package: agent-compliance
python-version: "3.10"
- package: agent-runtime
python-version: "3.10"
- package: agent-lightning
python-version: "3.10"
env:
RUN_TESTS: ${{ needs.changes.outputs.python == 'true' || github.event_name == 'schedule' }}
steps:
- name: Skip (no Python changes)
if: env.RUN_TESTS != 'true'
run: echo "No Python changes — skipping tests"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: env.RUN_TESTS == 'true'
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
if: env.RUN_TESTS == 'true'
with:
python-version: ${{ matrix.python-version }}
- name: Install local sibling dependencies
if: env.RUN_TESTS == 'true'
run: |
# agent-os depends on agent_primitives (local, not on PyPI at >=3.x)
# and agentmesh (test_cmd_sign.py imports agentmesh.marketplace)
if [ "${{ matrix.package }}" = "agent-os" ]; then
pip install --no-cache-dir -e agent-governance-python/agent-primitives
pip install --no-cache-dir -e agent-governance-python/agent-mesh
fi
- name: Install ${{ matrix.package }}
if: env.RUN_TESTS == 'true'
working-directory: agent-governance-python/${{ matrix.package }}
run: |
pip install --no-cache-dir -e ".[dev]" 2>/dev/null || pip install --no-cache-dir -e ".[test]" 2>/dev/null || pip install --no-cache-dir -e . # Install local package (Scorecard: pinned via pyproject.toml)
pip install --no-cache-dir pytest==8.4.1 pytest-asyncio==0.26.0 2>/dev/null || true
- name: Test ${{ matrix.package }}
if: env.RUN_TESTS == 'true'
working-directory: agent-governance-python/${{ matrix.package }}
run: pytest tests/ -q --tb=short
# ── PyPI package build (only when Python files change) ────────────────
build-pypi:
needs: changes
if: needs.changes.outputs.python == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package:
[
agent-os,
agent-mesh,
agent-hypervisor,
agent-sre,
agent-compliance,
agent-runtime,
agent-lightning,
]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Install build tools
run: pip install --no-cache-dir build==1.2.2 setuptools==75.8.0
- name: Build ${{ matrix.package }}
working-directory: agent-governance-python/${{ matrix.package }}
run: python -m build
- name: Verify wheel
working-directory: agent-governance-python/${{ matrix.package }}
run: ls -la dist/*.whl
# ── Python dependency safety (only when Python files change) ──────────
security:
needs: changes
if: needs.changes.outputs.python == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Install safety
run: |
pip install --no-cache-dir safety==3.2.1
- name: Check dependencies
env:
GIT_TERMINAL_PROMPT: "0"
run: |
for pkg in agent-os agent-mesh agent-hypervisor agent-sre agent-compliance agent-runtime agent-lightning; do
echo "=== $pkg ==="
cd agent-governance-python/$pkg
pip install --no-cache-dir -e . 2>/dev/null || true # Install local package (Scorecard: pinned via pyproject.toml)
cd ../..
done
safety check 2>/dev/null || echo "Safety check completed with warnings"
# ── .NET build + test (only when C# files change) ────────────────────
test-dotnet:
needs: changes
if: needs.changes.outputs.dotnet == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: "8.0.x"
- name: Build .NET SDK
working-directory: agent-governance-dotnet
run: dotnet build --configuration Release --verbosity quiet
- name: Test .NET SDK
working-directory: agent-governance-dotnet
run: dotnet test --configuration Release --verbosity normal --no-build
- name: Pack NuGet
working-directory: agent-governance-dotnet
run: dotnet pack src/AgentGovernance/AgentGovernance.csproj --configuration
Release --no-build --output ./nupkg
- name: Verify NuGet package
working-directory: agent-governance-dotnet
run: ls -la ./nupkg/*.nupkg
- name: BinSkim — binary security analysis
working-directory: agent-governance-dotnet
run: |
dotnet tool install --global Microsoft.CodeAnalysis.BinSkim --version 4.* 2>/dev/null || true
BinSkim analyze "src/AgentGovernance/bin/Release/net8.0/*.dll" \
--output binskim-results.sarif \
--verbose 2>/dev/null || echo "BinSkim completed with warnings"
- name: Upload BinSkim SARIF
if: always()
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
with:
sarif_file: agent-governance-dotnet/binskim-results.sarif
category: binskim
continue-on-error: true
# ── Integration tests (only when integration packages change) ────────
test-integrations:
needs: changes
if: needs.changes.outputs.integrations == 'true' || needs.changes.outputs.python
== 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package: a2a-protocol
import-module: a2a_agentmesh
- package: crewai-agentmesh
import-module: crewai_agentmesh
- package: flowise-agentmesh
import-module: flowise_agentmesh
- package: haystack-agentmesh
import-module: haystack_agentmesh
- package: langchain-agentmesh
import-module: langchain_agentmesh
- package: langflow-agentmesh
import-module: langflow_agentmesh
- package: langgraph-trust
import-module: langgraph_trust
- package: llamaindex-agentmesh
import-module: llama_index.agent.agentmesh
- package: mcp-trust-proxy
import-module: mcp_trust_proxy
- package: nostr-wot
import-module: agentmesh_nostr_wot
- package: openai-agents-agentmesh
import-module: openai_agents_agentmesh
- package: openai-agents-trust
import-module: openai_agents_trust
- package: pydantic-ai-governance
import-module: pydantic_ai_governance
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Install ${{ matrix.package }}
working-directory: agent-governance-python/agentmesh-integrations/${{ matrix.package }}
run: |
pip install --no-cache-dir -e ".[dev]" 2>/dev/null || pip install --no-cache-dir -e ".[test]" 2>/dev/null || pip install --no-cache-dir -e . # Install local package (Scorecard: pinned via pyproject.toml)
pip install --no-cache-dir pytest==8.4.1 pytest-asyncio==0.26.0 2>/dev/null || true
- name: Validate Python syntax
working-directory: agent-governance-python/agentmesh-integrations/${{ matrix.package }}
run: |
python -c "
import ast, glob, sys
errors = 0
for f in glob.glob('**/*.py', recursive=True):
try:
with open(f) as fh:
ast.parse(fh.read(), f)
except SyntaxError as e:
print(f'FAIL {f}: {e}')
errors += 1
if errors:
sys.exit(1)
print('All Python files parse successfully')
"
- name: Smoke test — import ${{ matrix.import-module }}
run: python -c "import ${{ matrix.import-module }}"
continue-on-error: true
- name: Run tests
working-directory: agent-governance-python/agentmesh-integrations/${{ matrix.package }}
run: |
if [ -d tests ]; then
pytest tests/ -q --tb=short
else
echo "No tests/ directory — smoke import passed"
fi
# ── Dependency confusion scan (always runs — security gate) ──────────
dep-confusion-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Dependency confusion scan
run: python3 scripts/check_dependency_confusion.py --strict
# ── Notebook pip-install audit (always runs — security gate) ─────────
notebook-pip-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Notebook pip-install audit
run: |
python3 -c "
import json, glob, sys, re
REGISTERED = {
'agent-os-kernel','agentmesh-platform','agent-hypervisor',
'agentmesh-runtime','agent-sre','agent-governance-toolkit',
'agentmesh-lightning','agentmesh-marketplace',
'pydantic','pyyaml','cryptography','pynacl','click','rich',
'httpx','aiohttp','fastapi','uvicorn','structlog','numpy',
'scipy','openai','anthropic','langchain','crewai',
'streamlit','plotly','pandas','networkx','aioredis',
'langchain-openai','langchain-core','python-dotenv',
'agent-primitives','agentmesh-memory','emk',
}
bad = []
for nb in glob.glob('**/*.ipynb', recursive=True):
if 'node_modules' in nb or '.ipynb_checkpoints' in nb:
continue
try:
with open(nb, encoding='utf-8') as fh:
cells = json.load(fh)['cells']
except Exception:
continue
for c in cells:
for line in c.get('source', []):
if 'pip install' in line and not line.strip().startswith('#') and not line.strip().startswith('>'):
pkgs = re.findall(r'(?:pip install\s+)(.+)', line)
if pkgs:
for p in pkgs[0].split():
name = re.sub(r'[^a-zA-Z0-9._-]', '', re.sub(r'\[.*\]', '', p))
if (name and not name.startswith('-') and not name.startswith('.')
and not name.startswith('http') and name not in REGISTERED
and not name.startswith('--')):
bad.append(f'{nb}: {name}')
if bad:
print('UNREGISTERED PACKAGES IN NOTEBOOKS:')
for b in bad:
print(f' {b}')
sys.exit(1)
print(f'OK: All notebook pip install packages are registered')
"
# ── Markdown link check (checks all .md files in the repo)
# Approach adapted from gaurav-nelson/github-action-markdown-link-check
markdown-link-check:
needs: changes
if: github.event_name == 'schedule' || needs.changes.outputs['docs-only'] == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Markdown link check
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'no'
config-file: '.github/linters/markdown-link-check.json'
# Configure it to check all .md files in the repo
check-modified-files-only: 'no'
base-branch: main
# ── Workflow security audit (only when workflows change) ─────────────
workflow-security:
needs: changes
if: needs.changes.outputs.workflows == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Audit pull_request_target workflows
run: |
echo "=== Checking pull_request_target safety ==="
UNSAFE=0
for f in .github/workflows/*.yml; do
if grep -q 'pull_request_target' "$f"; then
# Only flag if actions/checkout has ref: pointing to head (unsafe)
# Uses awk to check checkout blocks specifically, not unrelated lines
if awk '/actions\/checkout/{found=1} found && /ref:.*head\.(ref|sha)/{print; exit 1}' "$f" 2>/dev/null; then
echo "OK: $f (pull_request_target, base-only checkout)"
else
echo "UNSAFE: $f checks out PR head in pull_request_target context"
UNSAFE=1
fi
fi
done
if [ $UNSAFE -eq 1 ]; then exit 1; fi
# ── TypeScript integration tests (only when TS files change) ─────────
test-integrations-ts:
needs: changes
if: needs.changes.outputs.typescript == 'true' || github.event_name ==
'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
- name: Install mastra-agentmesh
working-directory: agent-governance-python/agentmesh-integrations/mastra-agentmesh
run: npm ci 2>/dev/null || npm install --ignore-scripts
- name: Lint mastra-agentmesh
working-directory: agent-governance-python/agentmesh-integrations/mastra-agentmesh
run: npm run lint 2>/dev/null || true
- name: Test mastra-agentmesh
working-directory: agent-governance-python/agentmesh-integrations/mastra-agentmesh
run: npm test
- name: Install copilot-governance
working-directory: agent-governance-python/agentmesh-integrations/copilot-governance
run: npm ci 2>/dev/null || npm install --ignore-scripts
- name: Lint copilot-governance
working-directory: agent-governance-python/agentmesh-integrations/copilot-governance
run: npm run lint 2>/dev/null || true
- name: Test copilot-governance
working-directory: agent-governance-python/agentmesh-integrations/copilot-governance
run: npm test
# ── npm package build + test (only when TS files change) ──────────────
build-npm:
needs: changes
if: needs.changes.outputs.typescript == 'true' || github.event_name ==
'schedule'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: agentmesh-mcp-proxy
path: agent-governance-python/agent-mesh/packages/mcp-proxy
- name: agent-governance-sdk
path: agent-governance-typescript
- name: agentmesh-api
path: agent-governance-python/agent-mesh/services/api
- name: agent-os-copilot-extension
path: agent-governance-python/agent-os/extensions/copilot
- name: agentos-mcp-server
path: agent-governance-python/agent-os/extensions/mcp-server
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
- name: Install dependencies
working-directory: ${{ matrix.path }}
run: npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps
--ignore-scripts
- name: Build ${{ matrix.name }}
working-directory: ${{ matrix.path }}
run: npm run build
- name: Test ${{ matrix.name }}
working-directory: ${{ matrix.path }}
run: npm test 2>/dev/null || echo "No tests configured"
# ── Rust build + test (only when Rust files change) ──────────────────
build-rust:
needs: changes
if: needs.changes.outputs.rust == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Build
working-directory: agent-governance-rust
run: cargo build --release --workspace
- name: Test
working-directory: agent-governance-rust
run: cargo test --release --workspace
# ── Go build + test (only when Go files change) ─────────────────────
build-go:
needs: changes
if: needs.changes.outputs.go == 'true' || github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.22"
- name: Build
working-directory: agent-governance-golang
run: go build ./...
- name: Test
working-directory: agent-governance-golang
run: go test ./...
- name: Vet
working-directory: agent-governance-golang
run: go vet ./...
# ── Docker integrated test (matches the documented contributor flow) ──
# Runs the exact two commands from CONTRIBUTING.md "Docker Quickstart" so
# contributor-facing breakage is caught before merge to main. Catches
# shim/canonical drift, Dockerfile/compose drift, line-ending regressions,
# and any bug that only surfaces in the integrated install shape.
docker-compose-test:
needs: changes
if: |
needs.changes.outputs.docker == 'true' ||
needs.changes.outputs.python == 'true' ||
github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Diagnostic — Docker / Compose / Buildx versions
run: |
docker version
docker compose version
docker buildx version
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Run documented contributor flow
run: |
# Compute and validate host UID/GID so the test container can write
# to the bind-mounted workspace. Validate they are pure positive
# integers before exporting (defense-in-depth: the values come from
# `id` on the runner, but we still refuse to forward anything that
# is not a non-negative integer to docker compose).
HOST_UID="$(id -u)"
HOST_GID="$(id -g)"
if ! [[ "${HOST_UID}" =~ ^[0-9]+$ ]] || ! [[ "${HOST_GID}" =~ ^[0-9]+$ ]]; then
echo "::error::Refusing to run: id -u/-g returned non-numeric value (uid='${HOST_UID}' gid='${HOST_GID}')"
exit 1
fi
export HOST_UID HOST_GID
echo "Running compose with HOST_UID=${HOST_UID} HOST_GID=${HOST_GID}"
docker compose up --build dev -d
docker compose run --rm test
- name: Collect dev container logs
if: always()
run: |
# Capture and redact obvious secret-shaped tokens before the log is
# uploaded as a build artifact. The dev container does not receive
# GitHub secrets, but we still scrub conservatively so that any
# accidental leakage from a future change does not surface here.
docker compose logs dev > dev.log.raw 2>&1 || true
sed -E \
-e 's/(gh[pousr]_[A-Za-z0-9]{20,})/[REDACTED-GH-TOKEN]/g' \
-e 's/(sk-[A-Za-z0-9_-]{20,})/[REDACTED-API-KEY]/g' \
-e 's/(AKIA[A-Z0-9]{16})/[REDACTED-AWS-ACCESS-KEY]/g' \
-e 's/((password|secret|token|api_?key)[[:space:]]*[:=][[:space:]]*)[^[:space:]"]+/\1[REDACTED]/Ig' \
dev.log.raw > dev.log
rm -f dev.log.raw
- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-compose-test-logs
path: dev.log
retention-days: 7
- name: Tear down
if: always()
run: docker compose down -v
# ── CI Gate — required status check that handles skipped jobs ────────
# When path-filters skip jobs (e.g. docs-only PRs skip tests), those
# jobs report "skipped" which doesn't satisfy required status checks.
# This gate job always runs, checks that no jobs FAILED, and reports
# success. Configure this as the single required status check.
ci-complete:
if: always()
needs:
[
changes,
lint,
test,
build-pypi,
security,
test-dotnet,
test-integrations,
dep-confusion-scan,
notebook-pip-audit,
workflow-security,
test-integrations-ts,
build-npm,
build-rust,
build-go,
docker-compose-test,
markdown-link-check,
]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check job results
env:
NEEDS: ${{ toJSON(needs) }}
run: |
echo "Job results: $NEEDS"
set +e
failed=$(printf '%s' "$NEEDS" | python3 scripts/ci_complete_check.py)
status=$?
set -e
if [ "$status" -eq 1 ]; then
echo "::error::Failed jobs: $failed"
{
echo "## ci-complete: failures"
echo ""
echo "Failed jobs:"
echo "$failed" | tr ',' '\n' | sed 's/^/- /'
} >> "$GITHUB_STEP_SUMMARY"
exit 1
elif [ "$status" -ne 0 ]; then
echo "::error::Unable to parse ci-complete dependency results"
exit "$status"
fi
# AC3.4 — docker-compose-test must not be silently skipped on relevant PRs
docker_changed='${{ needs.changes.outputs.docker }}'
python_changed='${{ needs.changes.outputs.python }}'
docker_test_result='${{ needs.docker-compose-test.result }}'
event_name='${{ github.event_name }}'
if [ "$docker_changed" = "true" ] || [ "$python_changed" = "true" ] || [ "$event_name" = "schedule" ]; then
if [ "$docker_test_result" = "skipped" ]; then
echo "::error::docker-compose-test was skipped on a PR that touched docker- or python-relevant paths."
echo "::error::This violates AC3.4 — the gate must run when changes.outputs.docker or python is true."
exit 1
fi
fi
echo "All required jobs succeeded."