chore(deps): update websockets requirement from >=13 to >=16.1.1 in /sdk/python #57
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] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| # Never cancel an in-progress run on main: with no branch protection (issue #52), a | |
| # cancelled main run would leave a commit with no completed CI. Only PR/branch runs | |
| # are superseded by newer pushes. | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| guard: | |
| name: No internal/confidential content | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # The tree scan catches the final checkout; full history is needed to catch a | |
| # credential added in one PR commit and deleted in a later commit. | |
| fetch-depth: 0 | |
| - run: bash scripts/check-no-internal.sh --self-test | |
| - run: bash scripts/check-no-internal.sh | |
| - name: Scan every commit added by this change | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| PUSH_BEFORE_SHA: ${{ github.event.before }} | |
| PUSH_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$EVENT_NAME" == "pull_request" ]]; then | |
| range="$PR_BASE_SHA..$PR_HEAD_SHA" | |
| elif [[ -n "$PUSH_BEFORE_SHA" && ! "$PUSH_BEFORE_SHA" =~ ^0+$ ]]; then | |
| range="$PUSH_BEFORE_SHA..$PUSH_SHA" | |
| elif git rev-parse HEAD^ >/dev/null 2>&1; then | |
| range="HEAD^..HEAD" | |
| else | |
| range="HEAD" | |
| fi | |
| bash scripts/check-no-internal.sh --history "$range" | |
| schema: | |
| name: Schema sanity | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Validate JSON schemas (parse + JSON-Schema metaschema) | |
| run: | | |
| shopt -s globstar nullglob | |
| files=(schema/**/*.json schema/*.json) | |
| # Fail closed: the contract schema must exist (it is load-bearing), and each file | |
| # must be both valid JSON AND a structurally valid JSON Schema (not just parseable). | |
| if [ ${#files[@]} -eq 0 ]; then echo "::error::no schema files found under schema/"; exit 1; fi | |
| pip install -q jsonschema | |
| python3 - "${files[@]}" <<'PY' | |
| import json, sys | |
| from jsonschema.validators import validator_for | |
| for f in sys.argv[1:]: | |
| schema = json.load(open(f)) | |
| validator_for(schema).check_schema(schema) # raises on a malformed schema | |
| print("valid JSON Schema:", f) | |
| PY | |
| shinkend: | |
| name: shinkend (Rust) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: present | |
| run: | | |
| if [ -f shinkend/Cargo.toml ]; then echo "exists=true" >> "$GITHUB_OUTPUT"; else echo "exists=false" >> "$GITHUB_OUTPUT"; fi | |
| - if: steps.present.outputs.exists == 'true' | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - if: steps.present.outputs.exists == 'true' | |
| working-directory: shinkend | |
| run: | | |
| cargo fmt --all -- --check | |
| cargo clippy --locked --all-targets -- -D warnings | |
| cargo test --locked --all | |
| - if: steps.present.outputs.exists == 'false' | |
| run: echo "::error::shinkend/Cargo.toml missing — this gate must not be skipped"; exit 1 | |
| contract: | |
| name: v0.0.1 contract gate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: present | |
| run: | | |
| if [ -f sdk/python/tests/test_contract.py ]; then echo "go=true" >> "$GITHUB_OUTPUT"; else echo "go=false" >> "$GITHUB_OUTPUT"; fi | |
| - if: steps.present.outputs.go == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - if: steps.present.outputs.go == 'true' | |
| working-directory: sdk/python | |
| run: | | |
| python -m pip install -U pip | |
| pip install -e ".[dev]" | |
| pytest tests/test_contract.py -q # v0.0.1 contract: fails on schema/runtime drift | |
| - if: steps.present.outputs.go == 'false' | |
| run: echo "::error::sdk/python/tests/test_contract.py missing — the contract gate must not be skipped"; exit 1 | |
| sdk-python: | |
| name: SDK (Python) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # pyproject declares requires-python >=3.10; exercise the floor and a recent ver. | |
| python-version: ["3.10", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - run: test -d sdk/python || { echo "::error::sdk/python missing — this gate must not be skipped"; exit 1; } | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - working-directory: sdk/python | |
| run: | | |
| python -m pip install -U pip | |
| pip install -e ".[dev]" | |
| ruff check . | |
| ruff format --check . # the Makefile `fmt` target runs `ruff format`; keep CI in sync | |
| pytest -q | |
| sdk-typescript: | |
| name: SDK (TypeScript) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 # was @v6 elsewhere; aligned so checkout version doesn't drift | |
| - run: test -f sdk/typescript/package.json || { echo "::error::sdk/typescript missing — this gate must not be skipped"; exit 1; } | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| - run: npm ci | |
| - run: | | |
| npm run ts:check | |
| npm run ts:test | |
| integration-linux: | |
| name: Linux integration (Xvfb) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: present | |
| run: | | |
| if [ -f shinkend/Cargo.toml ] && [ -f scripts/m1_smoke.py ]; then echo "go=true" >> "$GITHUB_OUTPUT"; else echo "go=false" >> "$GITHUB_OUTPUT"; fi | |
| - if: steps.present.outputs.go == 'true' | |
| uses: dtolnay/rust-toolchain@stable | |
| - if: steps.present.outputs.go == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - if: steps.present.outputs.go == 'true' | |
| run: sudo apt-get update && sudo apt-get install -y xvfb xdotool x11-apps | |
| - if: steps.present.outputs.go == 'true' | |
| run: cargo build --manifest-path shinkend/Cargo.toml | |
| - if: steps.present.outputs.go == 'true' | |
| run: pip install -e sdk/python | |
| - name: live pointer-action smoke (move to 300,200 → verify with xdotool) | |
| if: steps.present.outputs.go == 'true' | |
| run: | | |
| set -euo pipefail | |
| export DISPLAY=:99 | |
| Xvfb :99 -screen 0 1280x800x24 -ac & | |
| xvfb_ready=0 | |
| for _ in $(seq 1 50); do xdotool getmouselocation >/dev/null 2>&1 && { xvfb_ready=1; break; }; sleep 0.1; done | |
| [ "$xvfb_ready" = 1 ] || { echo "::error::Xvfb (:99) never became ready"; exit 1; } | |
| TOKEN=$(openssl rand -hex 32) | |
| export SHK_TOKEN="$TOKEN" | |
| # This job explicitly exercises launch_app, which shares the default-off | |
| # arbitrary process-spawn boundary with exec. | |
| SHINKEND_ADDR=127.0.0.1:8765 SHINKEND_TOKEN="$TOKEN" SHINKEND_ENABLE_EXEC=1 ./shinkend/target/debug/shinkend & | |
| sleep 1 | |
| python scripts/m1_smoke.py 127.0.0.1:8765 | |
| eval "$(xdotool getmouselocation --shell)" | |
| echo "cursor at X=$X Y=$Y" | |
| test "$X" = "300" && test "$Y" = "200" && echo "✅ live X11 pointer action verified" | |
| if [ -f scripts/screencast_smoke.py ]; then | |
| python scripts/screencast_smoke.py | |
| echo "✅ live screencast frames verified" | |
| fi | |
| if [ -f scripts/window_smoke.py ]; then | |
| xclock -geometry 200x200+40+40 & | |
| WID="" | |
| for _ in $(seq 1 50); do | |
| WID=$(xdotool search --class xclock 2>/dev/null | head -1 || true) | |
| [ -z "$WID" ] && WID=$(xdotool search --name xclock 2>/dev/null | head -1 || true) | |
| [ -n "$WID" ] && break; sleep 0.1 | |
| done | |
| test -n "$WID" || { echo "xclock window not found"; exit 1; } | |
| SHK_WINDOW="$WID" python scripts/window_smoke.py | |
| echo "✅ live focused-window capture verified (window:$WID)" | |
| fi | |
| if [ -f scripts/action_surface_smoke.py ]; then | |
| SHK_EXPECT_WINDOW="${WID:-}" python scripts/action_surface_smoke.py | |
| eval "$(xdotool getmouselocation --shell)" | |
| echo "cursor at X=$X Y=$Y after drag" | |
| test "$X" = "640" && test "$Y" = "360" && echo "✅ live drag verified (pointer parked at the drop point)" | |
| echo "✅ live action surface verified (drag + mouse_down/up + observe + list_windows)" | |
| fi | |
| if [ -f scripts/clipboard_app_smoke.py ]; then | |
| python scripts/clipboard_app_smoke.py | |
| echo "✅ live desktop verbs verified (clipboard set→get + launch→list_windows→activate, WM-less fallback paths)" | |
| fi | |
| - if: steps.present.outputs.go == 'false' | |
| run: echo "::error::shinkend or a smoke script is missing — this gate must not be skipped"; exit 1 | |
| sdk-wheel: | |
| name: SDK wheel install | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: present | |
| run: | | |
| if [ -f sdk/python/pyproject.toml ]; then echo "go=true" >> "$GITHUB_OUTPUT"; else echo "go=false" >> "$GITHUB_OUTPUT"; fi | |
| - if: steps.present.outputs.go == 'true' | |
| name: packaged SDK schemas match source of truth | |
| run: | | |
| diff -q schema/aci.schema.json sdk/python/src/shinken/schemas/aci.schema.json | |
| echo "packaged ACI schema in sync with schema/" | |
| - if: steps.present.outputs.go == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - if: steps.present.outputs.go == 'true' | |
| name: build wheel, install in a clean venv, validate WITHOUT the repo schema/ | |
| run: | | |
| set -euo pipefail | |
| python -m pip install -U pip build | |
| python -m build --wheel sdk/python --outdir dist | |
| python -m venv /tmp/venv | |
| /tmp/venv/bin/pip install -U pip | |
| /tmp/venv/bin/pip install dist/*.whl jsonschema | |
| cd /tmp # run outside the repo so repo-root schema/ is unreachable | |
| /tmp/venv/bin/python -c "from shinken import protocol; protocol.validate({'type':'ping','t':1.0}); print('packaged schema validate OK')" | |
| /tmp/venv/bin/shinken --version | |
| - if: steps.present.outputs.go == 'false' | |
| run: echo "::error::sdk/python/pyproject.toml missing — this gate must not be skipped"; exit 1 | |
| docker: | |
| name: Docker sandbox image | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: present | |
| run: | | |
| if [ -f images/linux/Dockerfile ] && [ -f scripts/docker_smoke.py ]; then echo "go=true" >> "$GITHUB_OUTPUT"; else echo "go=false" >> "$GITHUB_OUTPUT"; fi | |
| - if: steps.present.outputs.go == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - if: steps.present.outputs.go == 'true' | |
| name: build the Linux sandbox image | |
| run: docker build -f images/linux/Dockerfile -t shinken/sandbox-linux . | |
| - if: steps.present.outputs.go == 'true' | |
| name: run the container, then handshake + screenshot off its Xvfb desktop | |
| run: | | |
| set -euo pipefail | |
| pip install -e sdk/python | |
| TOKEN=$(openssl rand -hex 16) | |
| trap 'docker rm -f shk >/dev/null 2>&1 || true' EXIT | |
| docker run -d --name shk -p 127.0.0.1:8765:8765 \ | |
| -e SHINKEND_TOKEN="$TOKEN" -e SHINKEND_ENABLE_EXEC=1 shinken/sandbox-linux | |
| shk_ready=0 | |
| for _ in $(seq 1 60); do | |
| SHK_TOKEN="$TOKEN" python - <<'PY' && { shk_ready=1; break; } || sleep 1 | |
| import os | |
| import shinken | |
| env = shinken.connect("127.0.0.1:8765", token=os.environ["SHK_TOKEN"]) | |
| try: | |
| env.ping() | |
| env.screenshot() | |
| finally: | |
| env.close() | |
| PY | |
| done | |
| [ "$shk_ready" = 1 ] || { echo "::error::shinkend never became reachable"; docker logs shk || true; exit 1; } | |
| SHK_TOKEN="$TOKEN" python scripts/docker_smoke.py || { docker logs shk; exit 1; } | |
| if [ -f scripts/docker_file_smoke.py ]; then | |
| SHK_TOKEN="$TOKEN" SHK_CONTAINER=shk python scripts/docker_file_smoke.py || { docker logs shk; exit 1; } | |
| fi | |
| if [ -f scripts/observe_smoke.py ]; then | |
| SHK_TOKEN="$TOKEN" SHK_CONTAINER=shk python scripts/observe_smoke.py || { docker logs shk; docker exec shk cat /tmp/atspi.log /tmp/zen.log 2>/dev/null || true; exit 1; } | |
| fi | |
| if [ -f scripts/clipboard_app_smoke.py ]; then | |
| # the WM-ful shape: openbox serves the EWMH _NET_ACTIVE_WINDOW paths | |
| SHK_TOKEN="$TOKEN" python scripts/clipboard_app_smoke.py || { docker logs shk; exit 1; } | |
| fi | |
| docker rm -f shk | |
| - if: steps.present.outputs.go == 'true' | |
| name: provider lifecycle smoke (DockerLocalProvider) | |
| run: | | |
| set -euo pipefail | |
| PYTHONPATH=sdk/python/src python scripts/sandbox_bench.py \ | |
| --provider docker \ | |
| --concurrency 1 \ | |
| --iterations 1 \ | |
| --cleanup-orphans \ | |
| --output /tmp/sandbox-bench.json \ | |
| --markdown-output /tmp/sandbox-bench.md | |
| - if: steps.present.outputs.go == 'true' | |
| name: live runtime-state smoke (checkpoint → fork → screenshot the fork) | |
| run: | | |
| set -euo pipefail | |
| # The headline differentiator (Docker disk-tier checkpoint/fork) had only mocked | |
| # coverage; this exercises a real checkpoint→fork→connect→screenshot and asserts a | |
| # full-resolution non-black frame off the fork — directly regression-testing the | |
| # start.sh stale-X-lock fix (a fork would otherwise screenshot a dead display). | |
| PYTHONPATH=sdk/python/src python - <<'PY' | |
| from shinken.providers.docker import DockerLocalProvider | |
| from shinken.providers.base import SandboxSpec | |
| prov = DockerLocalProvider(image="shinken/sandbox-linux") | |
| base = prov.create(SandboxSpec(screen_geometry="1280x800x24")) | |
| ckpt = None | |
| fork = None | |
| try: | |
| ckpt = prov.checkpoint(base) | |
| fork = prov.resume(ckpt) # materialize a replica from the golden checkpoint | |
| env = prov.connect(fork) | |
| try: | |
| shot = env.screenshot() | |
| finally: | |
| env.close() | |
| assert shot["png"][:8] == b"\x89PNG\r\n\x1a\n", "fork screenshot is not a PNG" | |
| assert shot["w"] == 1280 and shot["h"] == 800, f"fork booted at wrong geometry: {shot['w']}x{shot['h']}" | |
| print("✅ live checkpoint→fork→screenshot verified (clean 1280x800 desktop on the fork)") | |
| finally: | |
| for h in (fork, base): | |
| if h is not None: | |
| try: | |
| prov.destroy(h) | |
| except Exception: | |
| pass | |
| prov.cleanup_snapshots() | |
| PY | |
| - if: steps.present.outputs.go == 'true' | |
| name: E1 — scripted >=5-step real-app GUI task verified via guest state | |
| run: PYTHONPATH=sdk/python/src python scripts/scripted_task_smoke.py | |
| - if: steps.present.outputs.go == 'true' | |
| name: E9 — run_eval_forked over the Docker tier with a real-state verifier | |
| run: PYTHONPATH=sdk/python/src python scripts/forked_eval_smoke.py 3 | |
| - if: steps.present.outputs.go == 'false' | |
| run: echo "::error::Docker image or a smoke script is missing — this gate must not be skipped"; exit 1 |