From ebad6051a13d4f098d87ab9f52a6bb62f4b7b1e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Apr 2026 00:17:32 +0000 Subject: [PATCH 1/3] ci(adcp): add storyboard CI job for seller_agent.py compliance checks Adds a non-blocking storyboard CI job (continue-on-error: true) that boots examples/seller_agent.py and runs the @adcp/client storyboard suite on every PR. Uses curl-based HTTP readiness polling instead of lsof, bounds the wait with a 30s timeout + PID-alive-check, and guards json.load with a file-existence check. Artifact always uploaded. Non-blocking until seller-agent content gaps in #304 are resolved. Closes #305 https://claude.ai/code/session_01SSje6ebBHD85TWdQbEig9m --- .github/workflows/ci.yml | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 130b92267..069cf3e8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -330,3 +330,82 @@ jobs: echo " Numbered-variant class-name churn is expected; the semantic" echo " alias tests and drift-version-pin test guard the real surface." fi + + storyboard: + name: AdCP storyboard runner — examples/seller_agent.py + runs-on: ubuntu-latest + # Non-blocking until seller-agent content gaps in #304 are resolved. + # Promote to required once overall_status: pass and controller_detected: true. + continue-on-error: true + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Start seller agent + run: | + ADCP_PORT=3001 python examples/seller_agent.py & + AGENT_PID=$! + for i in $(seq 1 60); do + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 \ + http://localhost:3001/mcp 2>/dev/null || echo "000") + if [ "$HTTP_CODE" != "000" ]; then + echo "Seller agent ready (HTTP ${HTTP_CODE}, pid ${AGENT_PID})" + break + fi + if ! kill -0 "$AGENT_PID" 2>/dev/null; then + echo "Seller agent process died during startup" + exit 1 + fi + if [ "$i" -eq 60 ]; then + echo "Seller agent failed to start within 30s" + kill "$AGENT_PID" 2>/dev/null || true + exit 1 + fi + sleep 0.5 + done + + - name: Run storyboard suite + timeout-minutes: 5 + run: | + npx -y -p @adcp/client@latest adcp storyboard run \ + http://localhost:3001/mcp media_buy_seller \ + --json --allow-http \ + > storyboard-result.json + + - name: Assert pass + run: | + python -c " + import json, sys, pathlib + p = pathlib.Path('storyboard-result.json') + if not p.exists() or p.stat().st_size == 0: + print('storyboard-result.json missing or empty — runner produced no output') + sys.exit(1) + d = json.load(p.open()) + if d.get('overall_status') != 'pass': + print(json.dumps({k: d[k] for k in ('overall_status', 'summary', 'failures')}, indent=2)) + sys.exit(1) + if not d.get('controller_detected'): + print('controller_detected was false; check DemoStore overrides (see #304)') + sys.exit(1) + " + + - if: always() + uses: actions/upload-artifact@v4 + with: + name: storyboard-result + path: storyboard-result.json From aae5d94fc90d38a0b2466a65b1969124f413a53a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Apr 2026 00:29:44 +0000 Subject: [PATCH 2/3] ci(adcp): fix storyboard CI job after pre-PR review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - overall_status check: 'pass' → 'passing' (actual runner enum value) - Assert step: dump full JSON on failure instead of d[k] (avoids KeyError) - Artifact upload: add if-no-files-found: warn and run_attempt suffix - Comment: document @latest intent and 405-ok readiness check rationale https://claude.ai/code/session_01SSje6ebBHD85TWdQbEig9m --- .github/workflows/ci.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 069cf3e8d..2e73b08e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -335,7 +335,7 @@ jobs: name: AdCP storyboard runner — examples/seller_agent.py runs-on: ubuntu-latest # Non-blocking until seller-agent content gaps in #304 are resolved. - # Promote to required once overall_status: pass and controller_detected: true. + # Promote to required once overall_status: passing and controller_detected: true. continue-on-error: true steps: @@ -361,6 +361,8 @@ jobs: ADCP_PORT=3001 python examples/seller_agent.py & AGENT_PID=$! for i in $(seq 1 60); do + # Any HTTP response (including 405 on GET to a POST-only endpoint) + # means the server is up and accepting connections. HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 \ http://localhost:3001/mcp 2>/dev/null || echo "000") if [ "$HTTP_CODE" != "000" ]; then @@ -381,6 +383,9 @@ jobs: - name: Run storyboard suite timeout-minutes: 5 + # @adcp/client@latest is intentionally unpinned — this is AdCP's own CI + # running AdCP's own canonical runner. Tracking latest surfaces protocol + # drift as soon as it ships, which is the point of this job. run: | npx -y -p @adcp/client@latest adcp storyboard run \ http://localhost:3001/mcp media_buy_seller \ @@ -395,9 +400,10 @@ jobs: if not p.exists() or p.stat().st_size == 0: print('storyboard-result.json missing or empty — runner produced no output') sys.exit(1) - d = json.load(p.open()) - if d.get('overall_status') != 'pass': - print(json.dumps({k: d[k] for k in ('overall_status', 'summary', 'failures')}, indent=2)) + with p.open() as f: + d = json.load(f) + if d.get('overall_status') != 'passing': + print(json.dumps(d, indent=2)) sys.exit(1) if not d.get('controller_detected'): print('controller_detected was false; check DemoStore overrides (see #304)') @@ -407,5 +413,6 @@ jobs: - if: always() uses: actions/upload-artifact@v4 with: - name: storyboard-result + name: storyboard-result-${{ github.run_attempt }} path: storyboard-result.json + if-no-files-found: warn From 60375a93ed597f69f4c4ab9471b2d683313611da Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 29 Apr 2026 20:54:29 -0400 Subject: [PATCH 3/3] ci(adcp): use 127.0.0.1 instead of localhost for storyboard probes Acting on dx-expert review of this PR: On dual-stack hosts (and Ubuntu runners since actions/runner 2.300+), ``localhost`` resolves to ``::1`` first. uvicorn's default bind is IPv4-only, so the readiness probe and runner invocation each eat a connection-refused round-trip on ``::1`` before falling back to ``127.0.0.1``. Curl falls back automatically (so it still works), but it's wasteful and slightly fragile. Pin both call sites to ``127.0.0.1`` directly. The agent's bind address is unchanged (still ``0.0.0.0`` via ``ADCP_HOST`` default in #296); only the client-side address resolution changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e73b08e9..6d17551bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -364,7 +364,7 @@ jobs: # Any HTTP response (including 405 on GET to a POST-only endpoint) # means the server is up and accepting connections. HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 \ - http://localhost:3001/mcp 2>/dev/null || echo "000") + http://127.0.0.1:3001/mcp 2>/dev/null || echo "000") if [ "$HTTP_CODE" != "000" ]; then echo "Seller agent ready (HTTP ${HTTP_CODE}, pid ${AGENT_PID})" break @@ -388,7 +388,7 @@ jobs: # drift as soon as it ships, which is the point of this job. run: | npx -y -p @adcp/client@latest adcp storyboard run \ - http://localhost:3001/mcp media_buy_seller \ + http://127.0.0.1:3001/mcp media_buy_seller \ --json --allow-http \ > storyboard-result.json