Skip to content

Latest commit

 

History

History
228 lines (178 loc) · 8.48 KB

File metadata and controls

228 lines (178 loc) · 8.48 KB

CI pipeline

This document describes the five GitHub Actions workflows in .github/workflows/, what each one asserts, where its artifacts land, and the exact commands to reproduce each check locally. It also documents the one-time repository setup the release workflow depends on.

Workflow File Triggers
Lint lint.yml push to main, all pull requests
Schema drift schema-drift.yml push to main, all pull requests
CI (test matrix) ci.yml push to main, all pull requests
Container profile container.yml push to main, all pull requests
Release release.yml push of a tag matching v*

All five run with permissions: contents: read and a per-branch/ref concurrency group that cancels superseded runs (release does not cancel-in-progress, since a publish must run to completion).

lint.yml — lint

Triggers: push to main, pull requests.

What it asserts: reference/wcpd and conformance are ruff-clean — both the lint rule set (ruff check) and formatting (ruff format --check). No artifacts are produced; a non-zero exit fails the job.

Local reproduction:

python -m pip install ruff
ruff check reference/wcpd conformance
ruff format --check reference/wcpd conformance

To auto-fix formatting before committing:

ruff format reference/wcpd conformance

schema-drift.yml — schema-drift

Triggers: push to main, pull requests.

What it asserts: the checked-in schemas/ directory (the manifest schema and every per-operation schema under schemas/ops/) is byte-for-byte what wcpd's live operation registry would generate. This catches the case where someone edits an operation's params/result shape, scope, or annotations without regenerating the published schema files.

Local reproduction:

cd reference/wcpd
python -m pip install -e ".[dev]"
python -m pytest tests/test_schema_export.py -q

If it fails because you intentionally changed an operation, regenerate the schemas (see CONTRIBUTING.md) and commit the diff under schemas/.

ci.yml — ci

Triggers: push to main, pull requests.

What it asserts: the reference server and the conformance suite work across the full support matrix — ubuntu-latest, macos-latest, windows-latest, each on Python 3.11 and 3.12 (6 jobs, fail-fast: false so one OS/version failing doesn't hide others). Each job:

  1. Installs both packages editable: reference/wcpd[dev] and conformance[dev].
  2. Runs reference/wcpd's unit tests with an OS-specific marker filter so platform-specific tests (Linux D-Bus adapters, macOS AppleScript adapters, Windows UIA adapters, anything needing a display) only run where they apply.
  3. Boots a real wcpd server and runs the conformance suite against it end-to-end (not in-process), via scripts/run_conformance.sh on POSIX runners or scripts/run_conformance.ps1 on Windows.
  4. Uploads the conformance report as an artifact regardless of outcome.

Environment used by the job: WCP_JWT_SECRET=ci-test-secret, WCP_WORKSTATION_ID=ci-ws.

Artifacts: conformance-<os>-py<version> — the conformance-report.json produced by the boot-and-run script, uploaded with if-no-files-found: ignore (a job that fails before the script runs simply has no artifact).

Local reproduction — unit tests, per OS marker filter:

cd reference/wcpd
python -m pip install -e ".[dev]"

# on Linux
python -m pytest . -q -m "not macos and not windows and not display"
# on macOS
python -m pytest . -q -m "not linux and not windows and not display"
# on Windows
python -m pytest . -q -m "not linux and not macos and not display"

Local reproduction — end-to-end conformance run (boots the server for you):

# from the repo root, POSIX (Linux/macOS)
python -m pip install -e "reference/wcpd[dev]"
python -m pip install -e "conformance[dev]"
bash scripts/run_conformance.sh

# from the repo root, Windows
pwsh -File scripts/run_conformance.ps1

Both scripts set sane defaults (WCP_JWT_SECRET=ci-test-secret, WCP_WORKSTATION_ID=ci-ws, WCP_HOST=127.0.0.1, WCP_PORT=8977, a temp WCP_SANDBOX_ROOT, an audit log inside it), start uvicorn --factory wcpd.server:build_app, poll scripts/wait_for_http.py until /mcp answers, run wcp-conformance, write conformance-report.json in the current directory, tear the server down, and propagate the conformance exit code.

container.yml — container

Triggers: push to main, pull requests.

What it asserts: the shipped Dockerfile under reference/wcpd/ actually boots and is core-conformant, and that the headless container profile covers at minimum the wcp.fs, wcp.exec, and wcp.system capability families (the families a container with no display or window manager can realistically expose). It also dumps docker logs wcpd on failure for debugging.

Artifacts: container-conformancecontainer-conformance.json, uploaded with if-no-files-found: ignore.

Local reproduction:

docker build -t wcpd:ci reference/wcpd
docker run -d --name wcpd \
  -e WCP_JWT_SECRET=ci-test-secret \
  -e WCP_WORKSTATION_ID=ci-ws \
  -p 8977:8977 wcpd:ci

python -m pip install -e "conformance[dev]"
python scripts/wait_for_http.py http://127.0.0.1:8977/mcp 40

wcp-conformance \
  --endpoint http://127.0.0.1:8977/mcp \
  --jwt-secret ci-test-secret \
  --workstation-id ci-ws \
  --schemas-dir schemas \
  --json container-conformance.json

python - <<'PY'
import json
rep = json.load(open("container-conformance.json"))
assert rep["core_conformant"], "container image is not core-conformant"
fams = rep["family_verdicts"]
for required in ("wcp.fs", "wcp.exec", "wcp.system"):
    assert fams.get(required, False), f"{required} must pass in the container profile"
print("container profile OK:", sorted(fams))
PY

docker logs wcpd     # inspect if something failed
docker rm -f wcpd     # tear down

release.yml — release

Triggers: push of a tag matching v* (e.g. v0.1.0).

What it does:

  1. build — builds sdists and wheels for both reference/wcpd and conformance with python -m build, uploads them as the dist artifact.
  2. publish-pypi — downloads dist, publishes both distributions to PyPI using PyPI trusted publishing (OIDC; id-token: write permission, no long-lived secret). Runs in the pypi GitHub Environment.
  3. publish-image — logs in to GHCR with the built-in GITHUB_TOKEN (packages: write permission — no extra secret needed), builds reference/wcpd's Dockerfile, and pushes ghcr.io/<owner>/wcpd:<tag> and ghcr.io/<owner>/wcpd:latest.

Artifacts: dist (sdists + wheels for both packages), consumed by the two publish jobs; nothing else is uploaded.

Release gate — one-time setup required before the first tag

Publishing to PyPI via trusted publishing does not work out of the box; it requires repository/organization configuration that must exist before you push the first v* tag, or publish-pypi will fail:

  1. Register the trusted publisher on PyPI. On the PyPI project page (Publishing → Add a new publisher), register this GitHub repository (kchemorion/WorkstationCapabilityProtocol), the workflow filename (release.yml), and the environment name (pypi). PyPI will only accept the OIDC token if these three match exactly.
  2. Create a pypi GitHub Environment in the repository settings (Settings → Environments → New environment → pypi). The publish-pypi job targets environment: pypi; without the environment existing the job's OIDC claim won't match what PyPI expects, and you may also want to add required reviewers/protection rules here to gate who can trigger a real publish.
  3. GHCR needs no extra setup. publish-image uses the automatically provided secrets.GITHUB_TOKEN with packages: write; no PAT or trusted-publisher registration is required, only that the repository allows Actions to write packages (default for repos with Packages enabled).

Until step 1 and 2 are done, do not push a v* tag expecting a successful PyPI publish — the build and GHCR jobs will still succeed, but publish-pypi will fail at the OIDC exchange.

Local reproduction (build only — nothing is published locally):

python -m pip install build
python -m build --outdir dist reference/wcpd
python -m build --outdir dist conformance