Skip to content

wave6: retire dead ctor entries (ALLOWLIST_DISCIPLINE §495, shared-diff fold) #410

wave6: retire dead ctor entries (ALLOWLIST_DISCIPLINE §495, shared-diff fold)

wave6: retire dead ctor entries (ALLOWLIST_DISCIPLINE §495, shared-diff fold) #410

Workflow file for this run

name: Test
# Runs the full per-port CI gate set via scripts/run-ci.sh:
# TEST → SIGNATURES → DRIFT → SURFACE-FRESH → NO-CHEAT → EMISSION
# Mirrors `bash scripts/run-ci.sh` exactly so there's no drift between local
# and CI behavior. Previously this workflow ran only `tsc --noEmit` + `npm
# test`, which left the EMISSION and NO-CHEAT gates unenforced on every TS PR
# — a behavioral-emission regression (wrong toValue()/to_dict() shape) could
# land green. run-ci.sh closes that hole.
env:
# Opt into Node.js 24 ahead of GitHub's 2026-06-02 default switch.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout signalwire-typescript
uses: actions/checkout@v7
with:
path: signalwire-typescript
- name: Checkout porting-sdk (mock servers + audit scripts + EMISSION corpus)
uses: actions/checkout@v7
with:
repository: signalwire/porting-sdk
# Coordinated-pass pin: 'main' normally; set the PORTING_SDK_REF repo variable
# to a wave branch to test a coordinated porting-sdk change, declared on the PR
# (see porting-sdk/COORDINATED_PASS.md). No revert commit.
ref: ${{ vars.PORTING_SDK_REF || 'main' }}
path: porting-sdk
token: ${{ secrets.PORTING_SDK_TOKEN }}
# The EMISSION gate compares this port's toValue() output against the
# signalwire-python oracle (FunctionResult), which pulls fastapi via
# signalwire/__init__. Check it out as a sibling so the gate has its
# reference.
- name: Checkout signalwire-python (EMISSION oracle)
uses: actions/checkout@v7
with:
repository: signalwire/signalwire-python
path: signalwire-python
# CI-PIN (Wave 1): the reference's Wave-1 changes (PY-1..9, incl PY-7
# request_options) live on signalwire-python wave/1-aplus (PR #67), not
# yet on main. REVERT to main when signalwire-python wave/1-aplus merges.
ref: ${{ vars.PORTING_SDK_REF || 'main' }}
# run-ci.sh's TEST/SIGNATURES/SURFACE-FRESH/EMISSION gates drive vitest /
# tsx; the script expects Node 24 (it prepends a local nvm path when
# present, else falls back to PATH — so set 24 here for the runner).
- uses: actions/setup-node@v7
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: signalwire-typescript/package-lock.json
- uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install Python test harnesses (mock_relay + mock_signalwire)
run: |
pip install -e porting-sdk/test_harness/mock_relay
pip install -e porting-sdk/test_harness/mock_signalwire
# scripts/run-py-lint.sh and run-py-format.sh fail LOUD when ruff is
# absent rather than skipping, so PY-LINT/PY-FMT cannot pass vacuously on
# a runner that lacks it — hence this explicit install rather than relying
# on it being preinstalled. Without it the gates red with "ruff not found
# on PATH", which is a missing CI dep, not a code finding.
#
# PINNED EXACT. An unbounded `pip install ruff` resolves whatever is newest
# at CI time, so a ruff release that adds a rule or changes a format
# heuristic turns PY-LINT/PY-FMT red on a commit that was green locally —
# the VERSION changed, not the code. 0.15.21 is the fleet-wide ruff
# (signalwire-python/perl/php pin the same). Bump it in every port at once,
# with the resulting fixes in the same commit.
- name: Install ruff (PY-LINT / PY-FMT gates)
run: |
pip install "ruff==0.15.21"
# Assert the pin actually took: pip can satisfy a spec from an
# unexpected index or cache, so verify rather than assume.
ruff --version
ruff --version | grep -qw '0\.15\.21' || { echo "FATAL: ruff is not the pinned 0.15.21" >&2; exit 1; }
- name: Install signalwire-python (EMISSION oracle) if not importable
run: |
if python -c "import signalwire.core.function_result" 2>/dev/null; then
echo "signalwire-python already importable; skipping install"
else
pip install ./signalwire-python
fi
# The SWAIG-HTTP-INVOKE gate (porting-sdk diff_port_swaig_http.py) builds its
# oracle via starlette.testclient.TestClient. On py3.14 CI the resolved
# starlette (1.x) turned the httpx-deprecation into a HARD requirement for the
# package literally named `httpx2` (a real PyPI package, v2.x) — plain
# `pip install httpx` (0.28) no longer satisfies it. Install httpx2 so the
# TestClient imports clean and the SWAIG-HTTP oracle builds.
- name: Install SWAIG-HTTP gate backend (starlette TestClient needs httpx2)
run: pip install httpx2
# ACTIONLINT gate (porting-sdk actionlint_gate.py) needs the actionlint
# binary and fails LOUD when it is absent (never a silent skip), so install
# it before run-ci. Official installer drops the binary in the cwd; move it
# onto PATH.
#
# VERSION PINNED. With no version argument the installer takes `latest`, so
# a new actionlint release (they add checks regularly) fails the ACTIONLINT
# gate on an UNCHANGED workflow file, while local dev — on whatever
# `brew install actionlint` last fetched — still passes. Keep in lockstep
# with SW_ACTIONLINT_VERSION in scripts/_env.sh.
- name: Install actionlint (ACTIONLINT gate)
run: |
bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.7.12
sudo mv ./actionlint /usr/local/bin/actionlint
actionlint --version
actionlint --version | grep -qw '1\.7\.12' || { echo "FATAL: actionlint is not the pinned 1.7.12" >&2; exit 1; }
- name: npm ci
working-directory: signalwire-typescript
run: npm ci
# Heavy doc-execution gates (SNIPPET-COMPILE/RUN, EXAMPLES-RUN — tier=nightly)
# are skipped on per-PR runs by default (they dominate run-ci wall time). But a
# PR that actually TOUCHES docs/examples/snippets must still be gated at PR time,
# so promote this run to the nightly tier when the diff touches those paths.
- name: Select gate tier (promote to nightly if PR touches docs/examples)
id: tier
working-directory: signalwire-typescript
run: |
tier=pr
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
# diff the PR against its base; grep the doc/example/snippet surface
if git fetch --no-tags --depth=1 origin "$base" 2>/dev/null \
&& git diff --name-only "$base"...HEAD 2>/dev/null \
| grep -qE '^(docs/|examples/|README|.*\.md$)'; then
tier=nightly
echo "PR touches docs/examples → running heavy doc gates (tier=nightly)"
fi
fi
echo "tier=$tier" >> "$GITHUB_OUTPUT"
- name: Run CI gate script (TEST → SIGNATURES → DRIFT → SURFACE-FRESH → NO-CHEAT → EMISSION)
working-directory: signalwire-typescript
env:
PORTING_SDK: ${{ github.workspace }}/porting-sdk
SW_CI_TIER: ${{ steps.tier.outputs.tier }}
# COORDINATED-PASS reads the live PR body/labels via 'gh api' to see a
# 'Coordinated-With:' declaration edited after the triggering push.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bash scripts/run-ci.sh