Group a holder list into the parties behind it #154
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 | |
| # Deliberately small. `scripts/ci-local.sh` runs ruff, mypy, pytest, the build, | |
| # and the fixture scan on the developer's machine before every commit, so | |
| # running them again here on every push buys a second opinion on the same | |
| # question at the cost of minutes per push. | |
| # | |
| # What remains is what a macOS/3.12 laptop genuinely cannot answer: | |
| # - Linux and the oldest supported Python, on every push (cheap, and the | |
| # version floor is where syntax mistakes surface); | |
| # - Windows, the rest of the version matrix, Docker, Nix, and uvx, on demand. | |
| # | |
| # The on-demand set is not decoration. Windows caught a socket guard that broke | |
| # the proactor event loop's self-pipe, and the Nix job is the only thing that | |
| # has ever verified the flake --- it was written on a machine without Nix and | |
| # was missing a keccak backend. Run `full` before tagging a release, after | |
| # touching packaging, or when anything reaches for the network or the loopback | |
| # server. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| full: | |
| description: "Also run Windows, the version matrix, Docker, Nix, and uvx" | |
| type: boolean | |
| default: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Third-party actions are pinned to commit SHAs, not tags. A tag is a mutable | |
| # pointer: whoever controls the repository can move v31 to different code, and | |
| # that code runs with this workflow's token. GitHub's own actions/* are left on | |
| # tags -- same reasoning would apply, but the trust anchor is the same platform | |
| # already running the job. | |
| permissions: | |
| contents: read | |
| env: | |
| # Deterministic Hypothesis runs; a property test that fails only on Tuesdays | |
| # is worse than no property test. | |
| HYPOTHESIS_PROFILE: ci | |
| PYTHONHASHSEED: "0" | |
| jobs: | |
| # The one job that runs on every push. Python 3.10 rather than 3.12 because | |
| # the local gates already cover 3.12 --- this is the floor, and the floor is | |
| # where a 3.12-only syntax feature shows up as a syntax error. | |
| check: | |
| name: lint, types & tests (linux, 3.10) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.10" | |
| cache: pip | |
| - run: pip install -e ".[dev,all]" | |
| - run: ruff check src tests | |
| - run: ruff format --check src tests | |
| - run: mypy | |
| # Note the absence of --network. The suite blocks sockets from the moment | |
| # tests/conftest.py is imported --- before collection, so module-level | |
| # code and every fixture scope are covered too. This is the second line | |
| # of defence, and the reason a green run here means something: a test | |
| # that quietly fell back to a live fetch would pass on a laptop. | |
| - run: pytest -q --tb=short | |
| # ------------------------------------------------------------------ on demand | |
| matrix: | |
| name: pytest ${{ matrix.python }} / ${{ matrix.os }} | |
| if: github.event_name == 'workflow_dispatch' && inputs.full | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { python: "3.11", os: ubuntu-latest } | |
| - { python: "3.12", os: ubuntu-latest } | |
| - { python: "3.13", os: ubuntu-latest } | |
| # Path handling and SQLite behaviour differ enough to be worth one | |
| # run each. Windows is the one that has actually caught something. | |
| - { python: "3.12", os: macos-latest } | |
| - { python: "3.12", os: windows-latest } | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| cache: pip | |
| - run: pip install -e ".[dev,all]" | |
| - run: pytest -q --tb=short | |
| loopback: | |
| name: local server (loopback only) | |
| if: github.event_name == 'workflow_dispatch' && inputs.full | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - run: pip install -e ".[dev,all]" | |
| # These bind a socket on 127.0.0.1, so they carry the `network` marker and | |
| # are deselected by default. They reach no remote host --- the marker is | |
| # about binding --- and the security model they cover is the reason the | |
| # browser extension is safe to run at all. | |
| - run: pytest tests/unit/test_local_server.py -q --network | |
| package: | |
| name: build, docker, nix, uvx | |
| if: github.event_name == 'workflow_dispatch' && inputs.full | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - run: pip install build twine | |
| - run: python -m build | |
| - name: validate package metadata | |
| run: twine check dist/* | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - run: docker build -t chainscope:ci . | |
| # A forensics tool reads untrusted data all day and mounts a case | |
| # directory; uid 0 is a bad trade for nothing. | |
| - name: it does not run as root | |
| run: | | |
| uid=$(docker run --rm --entrypoint id chainscope:ci -u) | |
| test "$uid" != "0" || { echo "container runs as root"; exit 1; } | |
| # Both halves. BSC has no keyless source -- there is no public Blockscout | |
| # instance for it -- so `doctor` must fail there; Ethereum does, since | |
| # Blockscout needs no credential, so it must pass. Asserting only the | |
| # first would be satisfied by a doctor that always fails. | |
| - name: doctor reports the missing key rather than passing silently | |
| run: | | |
| if docker run --rm chainscope:ci doctor --chain bsc; then | |
| echo "doctor exited 0 on BSC with no ETHERSCAN_API_KEY set"; exit 1 | |
| fi | |
| docker run --rm chainscope:ci doctor --chain bsc 2>&1 | grep -q ADDRESS_HISTORY | |
| - name: and passes where an unkeyed source covers it | |
| run: docker run --rm chainscope:ci doctor --chain eth | |
| - name: the end-to-end path works in the container | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/case" | |
| # As the invoking user. The image defaults to uid 1000 and a runner is | |
| # 1001, so without this the bind mount is unwritable -- exactly what a | |
| # user on any shared machine would hit. | |
| docker run --rm --user "$(id -u):$(id -g)" \ | |
| -v "$RUNNER_TEMP/case:/case" chainscope:ci \ | |
| tag 0x28C6c06298d514Db089934071355E5743bf21d60 \ | |
| -l "Binance 14" -t cex -C high -s "ci smoke test" | |
| test -f "$RUNNER_TEMP/case/.chainscope/store.db" | |
| - name: the MCP agent answers a handshake over stdio | |
| run: | | |
| # The whole reply, then matched. An earlier version piped through | |
| # `head -c 400` and grepped for "protocolVersion" -- the server's | |
| # `instructions` string pushes that field past the cut, so a perfectly | |
| # good handshake was reported as a failure. The container was fine; | |
| # the assertion was written against an assumed shape. | |
| reply=$(printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \ | |
| | docker run --rm -i --entrypoint chainscope-mcp chainscope:ci) | |
| echo "$reply" | grep -q '"result"' | |
| echo "$reply" | grep -q '"protocolVersion"' | |
| echo "$reply" | grep -q 'chainscope' | |
| - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31 | |
| with: | |
| extra_nix_config: "experimental-features = nix-command flakes" | |
| # The flake was written on a machine without Nix, so this is its only | |
| # verification. If it breaks, the flake is wrong -- not the job. | |
| - run: nix flake check --no-build | |
| - run: nix build .#chainscope --print-build-logs | |
| - run: ./result/bin/chainscope --help | |
| - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| # The zero-install path. If this breaks, the console scripts or the extras | |
| # are wrong, and the first thing a new user tries fails. | |
| - run: uvx --from '.[all]' chainscope --help |