chore: bump version to v1.34.0 #524
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| run_python_live: | |
| description: "Run Python SDK live E2E tests (requires internet, builds Go binary)" | |
| required: false | |
| default: "false" | |
| type: choice | |
| options: ["true", "false"] | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Detects whether this push/PR touches anything that needs the heavy Go jobs | |
| # (build, test, e2e, security, docker). Docs/meta-only changes set code=false | |
| # so those jobs skip — a skipped *required* job reports as "skipped", which | |
| # branch protection counts as passing, so docs PRs go green in seconds without | |
| # getting stuck on never-reported required checks. | |
| # | |
| # Allowlist semantics: a change skips heavy CI ONLY when EVERY changed file | |
| # matches a known-harmless pattern. Anything unrecognized (any .go, go.mod, | |
| # Dockerfile, Makefile, workflow, lenses/**, etc.) defaults to code=true → | |
| # full CI. Safer to over-run than to skip a security scan on real code. | |
| changes: | |
| name: Detect changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Classify changed files | |
| id: filter | |
| env: | |
| PR_BASE: ${{ github.event.pull_request.base.sha }} | |
| PUSH_BEFORE: ${{ github.event.before }} | |
| run: | | |
| set -euo pipefail | |
| # PR: diff against the base SHA. Push: diff against the previous SHA. | |
| base="${PR_BASE:-$PUSH_BEFORE}" | |
| # Fallback for first push / unknown base: run full CI. | |
| if [ -z "$base" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then | |
| echo "No usable diff base; running full CI." | |
| echo "code=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| files=$(git diff --name-only "$base" HEAD) | |
| echo "Changed files:"; echo "$files" | |
| # A file is "harmless" if it matches the allowlist below; if ANY | |
| # changed file is not harmless, run full CI (code=true). | |
| code=false | |
| while IFS= read -r f; do | |
| [ -z "$f" ] && continue | |
| case "$f" in | |
| *.md|docs/*|decks/*|assets/*|LICENSE|.gitignore|mkdocs.yml|overrides/*|.github/*_TEMPLATE*|.github/ISSUE_TEMPLATE/*) | |
| : ;; # harmless — keep checking | |
| *) | |
| code=true; break ;; # anything else → full CI | |
| esac | |
| done <<< "$files" | |
| echo "code=$code" >> "$GITHUB_OUTPUT" | |
| echo "Decision: code=$code" | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| - name: Check formatting (gofmt -s) | |
| run: | | |
| unformatted=$(gofmt -s -l $(git ls-files '*.go')) | |
| if [ -n "$unformatted" ]; then | |
| echo "::error::These files are not gofmt-clean (run 'make fmt'):" | |
| echo "$unformatted" | |
| exit 1 | |
| fi | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: v2.12 | |
| args: --timeout=5m | |
| test: | |
| name: Test (Go ${{ matrix.go-version }}) | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| go-version: ["1.25.11"] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| cache: true | |
| - name: Run tests with race detector and coverage | |
| run: go test -race -coverprofile=coverage.out -covermode=atomic -count=1 ./... | |
| - name: Upload coverage to Codecov | |
| if: matrix.go-version == '1.25.11' | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| files: ./coverage.out | |
| flags: unittests | |
| fail_ci_if_error: false | |
| env: | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Upload coverage artifact | |
| if: matrix.go-version == '1.25.11' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage | |
| path: coverage.out | |
| retention-days: 7 | |
| # Always-run doc/annotation drift gate. The full `test` job is skipped on | |
| # docs-only PRs (code=false), which would let a docs/TOOLS.md edit drift from | |
| # the registry undetected. This narrow, fast job runs on EVERY PR — including | |
| # docs-only ones — so docs↔code drift always fails CI. | |
| docs-drift: | |
| name: Docs drift gate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| - name: Verify tool docs/annotations match the registry | |
| run: | | |
| go test -count=1 \ | |
| -run 'TestToolsDocMatchesRegistry|TestOutputSchemaMatchesResponse|TestToolDescriptionQuality|TestAllToolsHaveAnnotations|TestAllToolsRegistered|TestAllToolsHaveOutputSchema|TestExternalContentToolsCarryTrustMarker' \ | |
| ./internal/tools/... | |
| # Always-run Python client drift gate. Regenerates the Python client from the | |
| # live Go schemas and fails if the committed files would change. Runs on every | |
| # PR so a Go schema change that was not followed by `make gen-python-client` | |
| # is caught even on code-only PRs that skip the docs gate. | |
| python-drift: | |
| name: Python client drift gate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Verify Python client matches Go schemas | |
| run: make check-python-drift | |
| # Always-run Python test suite. Runs on every PR so Python regressions are | |
| # caught even on code-only PRs that skip the docs gate. | |
| test-python: | |
| name: Python SDK tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install pytest | |
| run: python3 -m pip install --upgrade pytest | |
| - name: Run Python SDK tests | |
| run: python3 -m pytest tests/python/ --ignore=tests/python/test_live_e2e.py -v | |
| e2e: | |
| name: E2E (STDIO, network-free) | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| # The security + lifecycle e2e tests are network-free (they assert on | |
| # blocked schemes, private IPs, schema validation, session crypto, | |
| # secrets masking, the HTTP transport boundary, and the OAuth/scope gate) | |
| # so they run deterministically without API keys (DuckDuckGo is the | |
| # zero-config search fallback). Live-provider e2e tests self-skip when | |
| # keys are absent. | |
| # | |
| # The -run alternation MUST match the new test name prefixes | |
| # (TestHTTP_/TestOAuth_) or they pass locally but never run here — a silent | |
| # coverage gap. Enforced by naming convention. | |
| - name: Run E2E security + lifecycle suite (STDIO + HTTP + OAuth) | |
| run: go test -race -tags=e2e -count=1 -run 'TestSecurity_STDIO|TestMCPLifecycle|TestHTTP_|TestOAuth_' ./tests/e2e/... | |
| docker-smoke: | |
| name: Docker HTTP smoke | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| # Builds the shipped image and drives MCP over HTTP end-to-end | |
| # (initialize + web_search) with PORT set and NO stdin attached — the | |
| # regression guard for the HTTP-lifecycle fix (a container must not exit | |
| # on stdin EOF) and for the keyless zero-config startup path. No API keys: | |
| # DuckDuckGo is the zero-config search fallback. The image bakes in | |
| # Chromium but the smoke does not exercise the browser tier (deterministic, | |
| # fast). The script dumps `docker logs` on any failure for diagnosability. | |
| - name: Build image and smoke-test HTTP transport | |
| run: make docker-smoke | |
| build: | |
| name: Build (${{ matrix.goos }}/${{ matrix.goarch }}) | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| goos: [linux, darwin, windows] | |
| goarch: [amd64, arm64] | |
| exclude: | |
| - goos: windows | |
| goarch: arm64 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| - name: Build binary | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| CGO_ENABLED: "0" | |
| run: | | |
| EXTENSION="" | |
| if [ "${{ matrix.goos }}" = "windows" ]; then | |
| EXTENSION=".exe" | |
| fi | |
| go build \ | |
| -ldflags="-s -w -X main.version=${{ github.sha }}" \ | |
| -o "web-researcher-mcp-${{ matrix.goos }}-${{ matrix.goarch }}${EXTENSION}" \ | |
| ./cmd/web-researcher-mcp | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: binary-${{ matrix.goos }}-${{ matrix.goarch }} | |
| path: web-researcher-mcp-* | |
| retention-days: 7 | |
| security: | |
| name: Security | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| # govulncheck + gosec are pinned as go.mod tool directives, so versions | |
| # are identical to what contributors run locally via `make vuln` / `make sec`. | |
| - name: Run govulncheck | |
| run: go tool govulncheck ./... | |
| # gosec: Go security scanner (injection, weak crypto, SSRF sinks, unsafe | |
| # file ops). Tuned signal-only (G104 covered by golangci-lint errcheck; | |
| # tests excluded). Genuinely-safe sites carry inline `// #nosec` reasons. | |
| - name: Run gosec | |
| run: go tool gosec -exclude=G104 -exclude-dir=tests -quiet ./... | |
| # Manual-dispatch Python SDK live E2E tests. Builds the Go binary from source | |
| # and drives the SDK against real external APIs. Keyless providers (DuckDuckGo, | |
| # PubMed, World Bank, ClinicalTrials.gov) run unconditionally; keyed providers | |
| # skip when the relevant secret is absent. NOT part of the required CI gate — | |
| # opt-in only via workflow_dispatch to avoid flaky failures on rate-limits. | |
| python-live-e2e: | |
| name: Python SDK live E2E | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.run_python_live == 'true' }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.11" | |
| cache: true | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install pytest | |
| run: python3 -m pip install --upgrade pytest | |
| - name: Run Python SDK live E2E tests | |
| env: | |
| GOOGLE_CUSTOM_SEARCH_API_KEY: ${{ secrets.GOOGLE_CUSTOM_SEARCH_API_KEY }} | |
| GOOGLE_CUSTOM_SEARCH_ID: ${{ secrets.GOOGLE_CUSTOM_SEARCH_ID }} | |
| BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }} | |
| run: make test-python-live |