diff --git a/.github/workflows/verify-all-samples.yml b/.github/workflows/verify-all-samples.yml index 326c704..78fb1ac 100644 --- a/.github/workflows/verify-all-samples.yml +++ b/.github/workflows/verify-all-samples.yml @@ -99,14 +99,28 @@ jobs: ENTRY: ${{ matrix.sample.entry }} run: | set -euo pipefail + # `--no-build` is skipped only for python/semantic-kernel-tool-policy: + # its transitive dep pybars4 ships no wheel on PyPI (sdist only), so + # it must be built from source. `--locked` still applies. That + # example has a tests/ dir, so discover_samples.py always classifies + # it as `uv-pytest` — there is no `uv-main`/`py-script` case for it. + if [[ "$PWD" == *"semantic-kernel-tool-policy"* ]]; then + if [[ "$RUNNER" != "uv-pytest" ]]; then + echo "::error::semantic-kernel-tool-policy runner changed to $RUNNER — update this exception" + exit 1 + fi + uv sync --extra dev --locked + uv run --locked pytest tests/ -v + exit 0 + fi case "$RUNNER" in uv-pytest) - uv sync --extra dev - uv run pytest tests/ -v + uv sync --extra dev --locked --no-build + uv run --locked --no-build pytest tests/ -v ;; uv-main) - uv sync --extra dev - uv run python "$ENTRY" --mock + uv sync --extra dev --locked --no-build + uv run --locked --no-build python "$ENTRY" --mock ;; py-script) python "$ENTRY" @@ -149,7 +163,7 @@ jobs: set -euo pipefail case "$RUNNER" in pnpm-test) - pnpm install + pnpm install --ignore-scripts pnpm test ;; node-script) diff --git a/.github/workflows/verify-live.yml b/.github/workflows/verify-live.yml index 4243831..913cfbc 100644 --- a/.github/workflows/verify-live.yml +++ b/.github/workflows/verify-live.yml @@ -79,7 +79,7 @@ jobs: python-version: "3.13" # cp313 wheel gap (AAASM-4446) is part of what this lane must catch - name: Install the real published SDK (no shim) - run: pip install --pre "agent-assembly==0.0.1rc6" + run: "pip install --pre --only-binary=:all: 'agent-assembly==0.0.1rc6'" - name: Install and start the real aasm gateway run: bash .github/scripts/start-aasm.sh @@ -115,7 +115,7 @@ jobs: - name: Install the real published SDK (no stub) working-directory: scenarios/live-core-enforcement/node-agent - run: pnpm install + run: pnpm install --ignore-scripts - name: Install and start the real aasm gateway run: bash .github/scripts/start-aasm.sh diff --git a/.github/workflows/verify-node.yml b/.github/workflows/verify-node.yml index eadfcf8..c163e8a 100644 --- a/.github/workflows/verify-node.yml +++ b/.github/workflows/verify-node.yml @@ -37,7 +37,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck @@ -63,7 +63,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck @@ -89,7 +89,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck @@ -115,7 +115,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck @@ -141,7 +141,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck @@ -167,7 +167,7 @@ jobs: node-version: "20" - name: Install dependencies - run: pnpm install + run: pnpm install --ignore-scripts - name: Type-check run: pnpm typecheck diff --git a/.github/workflows/verify-python.yml b/.github/workflows/verify-python.yml index 9ae5021..ddc0ae0 100644 --- a/.github/workflows/verify-python.yml +++ b/.github/workflows/verify-python.yml @@ -32,10 +32,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v custom-tool-policy: name: custom-tool-policy @@ -51,10 +51,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v llamaindex-tool-policy: name: llamaindex-tool-policy @@ -70,10 +70,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v openai-agents-sdk: name: openai-agents-sdk @@ -89,10 +89,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v langchain-research-agent: name: langchain-research-agent @@ -108,10 +108,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v crewai-research-crew: name: crewai-research-crew @@ -127,10 +127,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v langgraph: name: langgraph @@ -146,10 +146,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v pydantic-ai: name: pydantic-ai @@ -165,10 +165,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v microsoft-agent-framework-tool-policy: name: microsoft-agent-framework-tool-policy @@ -187,13 +187,13 @@ jobs: # so CI installs `dev` only. The smoke tests importorskip it and the mock # demo runs without it — the offline path this repo's CI is meant to verify. - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v - name: Run offline mock demo - run: uv run python src/main.py --mock + run: uv run --locked --no-build python src/main.py --mock google-adk: name: google-adk @@ -209,10 +209,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v smolagents-tool-policy: name: smolagents-tool-policy @@ -228,10 +228,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v agno-tool-policy: name: agno-tool-policy @@ -247,10 +247,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v haystack-tool-policy: name: haystack-tool-policy @@ -266,10 +266,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v autogen-tool-policy: name: autogen-tool-policy @@ -285,13 +285,13 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v - name: Run offline demo - run: uv run python src/main.py + run: uv run --locked --no-build python src/main.py semantic-kernel-tool-policy: name: semantic-kernel-tool-policy @@ -307,13 +307,16 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + # `--no-build` (S8541) can't be used here: pybars4 (a transitive dep + # of semantic-kernel's templating) ships no wheel on PyPI, sdist only, + # so it must be built from source. `--locked` (S8544) still applies. + run: uv sync --extra dev --locked - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked pytest tests/ -v - name: Run offline demo - run: uv run python src/main.py + run: uv run --locked python src/main.py strands-agents-tool-policy: name: strands-agents-tool-policy @@ -329,10 +332,10 @@ jobs: version: "latest" - name: Install dependencies - run: uv sync --extra dev + run: uv sync --extra dev --locked --no-build - name: Run smoke tests - run: uv run pytest tests/ -v + run: uv run --locked --no-build pytest tests/ -v - name: Run offline demo - run: uv run python src/main.py + run: uv run --locked --no-build python src/main.py diff --git a/.github/workflows/verify-scenarios.yml b/.github/workflows/verify-scenarios.yml index 0424a65..c1f7447 100644 --- a/.github/workflows/verify-scenarios.yml +++ b/.github/workflows/verify-scenarios.yml @@ -155,7 +155,7 @@ jobs: with: python-version: "3.12" - name: Install PyYAML for policy validation - run: pip install pyyaml + run: "pip install --only-binary=:all: 'pyyaml==6.0.3'" - name: Validate example code + policy shape (no gateway required) run: python scenarios/live-core-enforcement/scripts/check-offline.py - name: Validate docker-compose config @@ -246,8 +246,8 @@ jobs: run: | if [ -d "scenarios/policy-enforcement/python" ]; then cd scenarios/policy-enforcement/python - uv sync --extra dev - uv run pytest tests/ -v + uv sync --extra dev --locked --no-build + uv run --locked --no-build pytest tests/ -v else echo "scenarios/policy-enforcement/python not yet present — skipping" fi @@ -273,7 +273,7 @@ jobs: run: | if [ -d "scenarios/policy-enforcement/node" ]; then cd scenarios/policy-enforcement/node - pnpm install + pnpm install --ignore-scripts pnpm run build else echo "scenarios/policy-enforcement/node not yet present — skipping" @@ -324,8 +324,8 @@ jobs: run: | if [ -d "scenarios/approval-gates/python" ]; then cd scenarios/approval-gates/python - uv sync --extra dev - uv run pytest tests/ -v + uv sync --extra dev --locked --no-build + uv run --locked --no-build pytest tests/ -v else echo "scenarios/approval-gates/python not yet present — skipping" fi @@ -351,7 +351,7 @@ jobs: run: | if [ -d "scenarios/approval-gates/node" ]; then cd scenarios/approval-gates/node - pnpm install + pnpm install --ignore-scripts pnpm run build else echo "scenarios/approval-gates/node not yet present — skipping" diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 0000000..55a0f77 --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,36 @@ +# SonarCloud Automatic Analysis configuration. +# NOTE: Automatic Analysis ignores sonar-project.properties; only this file +# (and wildcard-free literal paths, per SonarCloud's own restriction on this +# specific file) is honored. See docs: "Automatic analysis" > "Advanced +# configuration". + +# Path to sources +# sonar.sources= + +# Path to tests +# sonar.tests= + +# Source encoding +# sonar.sourceEncoding= + +# Exclusions for copy-paste detection. +# +# This is a tutorials/examples repo: every example under python/*, node/*, +# go/*, and snippets/* is intentionally self-contained and copy-paste-able, +# so each one re-implements its own local `policy.py` / `policy.ts` / +# `policy.go` (and a handful of `main.py` / `index.ts` / `test_smoke.py` +# files that follow the same per-framework template). That duplication is +# BY DESIGN — extracting a shared policy module would break the "copy one +# example directory and it just works" contract this repo exists to +# demonstrate. It is not a code smell to fix; it is excluded here rather +# than left to inflate the project's duplication rating. List is exact +# files (this property disallows wildcard patterns), taken from the +# duplication report as of 2026-07-23. +sonar.cpd.exclusions=go/langchaingo/policy.go,go/tool-policy/policy.go,node/custom-tool-policy/src/index.ts,node/custom-tool-policy/src/policy.ts,node/langchain-js-basic-agent/src/policy.ts,node/langgraph-js/src/policy.ts,node/mastra/src/policy.ts,node/openai-node-tool-policy/src/index.ts,node/openai-node-tool-policy/src/policy.ts,node/vercel-ai/src/policy.ts,python/agno-tool-policy/src/main.py,python/autogen-tool-policy/src/policy.py,python/crewai-research-crew/src/main.py,python/crewai-research-crew/src/policy.py,python/google-adk/src/policy.py,python/google-adk/tests/test_smoke.py,python/haystack-tool-policy/src/main.py,python/langchain-basic-agent/src/policy.py,python/langchain-research-agent/src/policy.py,python/langgraph/src/policy.py,python/microsoft-agent-framework-tool-policy/tests/test_smoke.py,python/pydantic-ai/src/policy.py,python/pydantic-ai/tests/test_smoke.py,python/semantic-kernel-tool-policy/src/policy.py,python/strands-agents-tool-policy/src/policy.py,snippets/node/custom-tool-policy.ts,snippets/node/openai-node-tool-policy.ts,snippets/python/agno-tool-policy.py,snippets/python/crewai-research-crew.py,snippets/python/haystack-tool-policy.py + +# Python version (for python projects only) +# sonar.python.version= + +# C++ standard version (for C++ projects only) +# If not specified, it defaults to the latest supported standard +# sonar.cfamily.reportingCppStandardOverride=c++98|c++11|c++14|c++17|c++20 diff --git a/python/haystack-tool-policy/uv.lock b/python/haystack-tool-policy/uv.lock index f1e4675..ee7cf9d 100644 --- a/python/haystack-tool-policy/uv.lock +++ b/python/haystack-tool-policy/uv.lock @@ -285,7 +285,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "agent-assembly", specifier = "==0.0.1rc6" }, - { name = "haystack-ai", specifier = ">=2.31.0,<3.0" }, + { name = "haystack-ai", specifier = ">=2.31.0,<4.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.1.1" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.15.1" }, ] diff --git a/scenarios/live-core-enforcement/python-agent/Dockerfile b/scenarios/live-core-enforcement/python-agent/Dockerfile index a83d4b5..6252086 100644 --- a/scenarios/live-core-enforcement/python-agent/Dockerfile +++ b/scenarios/live-core-enforcement/python-agent/Dockerfile @@ -8,11 +8,24 @@ FROM python:3.12-slim # Build deps for the native (PyO3) extension built by maturin, plus the Rust -# toolchain it needs — installed in one layer. +# toolchain it needs — installed in one layer. The rustup-init binary is +# fetched over TLS and its checksum verified against rustup's published +# SHA-256 digest before execution, rather than piping curl straight to sh. +ARG TARGETARCH RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential curl protobuf-compiler \ && rm -rf /var/lib/apt/lists/* \ - && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + && case "$TARGETARCH" in \ + amd64) RUST_ARCH=x86_64-unknown-linux-gnu ;; \ + arm64) RUST_ARCH=aarch64-unknown-linux-gnu ;; \ + *) echo "unsupported TARGETARCH: $TARGETARCH" >&2; exit 1 ;; \ + esac \ + && curl --proto '=https' --tlsv1.2 -sSfO "https://static.rust-lang.org/rustup/dist/${RUST_ARCH}/rustup-init" \ + && curl --proto '=https' --tlsv1.2 -sSfO "https://static.rust-lang.org/rustup/dist/${RUST_ARCH}/rustup-init.sha256" \ + && sha256sum -c rustup-init.sha256 \ + && chmod +x rustup-init \ + && ./rustup-init -y \ + && rm rustup-init rustup-init.sha256 ENV PATH="/root/.cargo/bin:${PATH}" # Install maturin (builds the native extension) and the published SDK in one @@ -20,8 +33,10 @@ ENV PATH="/root/.cargo/bin:${PATH}" # agent-assembly` is enough; against a source checkout, build the extension with # maturin. We install from PyPI here and rely on the platform wheel's native # layer; if you are developing against a local checkout, mount it and run -# `maturin develop` instead (see README). -RUN pip install --no-cache-dir maturin "agent-assembly==0.0.1rc6" +# `maturin develop` instead (see README). Versions are pinned and +# `--only-binary=:all:` forces wheel-only installs (both ship wheels for this +# platform), so no arbitrary sdist build script runs during install. +RUN pip install --no-cache-dir --only-binary=:all: "maturin==1.14.1" "agent-assembly==0.0.1rc6" WORKDIR /app COPY agent.py /app/agent.py