Skip to content

Commit fecf6a3

Browse files
authored
Merge branch 'main' into claude/issue-802-cut-6-0-beta
2 parents a083f82 + 52a9045 commit fecf6a3

8 files changed

Lines changed: 339 additions & 109 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,8 @@ jobs:
355355
storyboard:
356356
name: AdCP storyboard runner — examples/seller_agent.py
357357
runs-on: ubuntu-latest
358-
# Non-blocking until seller-agent content gaps in #304 are resolved.
359-
# Promote to required once overall_status: passing and controller_detected: true.
360-
continue-on-error: true
358+
# Blocking gate: examples/seller_agent.py is the Python-owned
359+
# reference target for bidirectional storyboard interop.
361360

362361
steps:
363362
- uses: actions/checkout@v6
@@ -384,93 +383,12 @@ jobs:
384383
restore-keys: |
385384
${{ runner.os }}-npm-
386385
387-
- name: Pre-install @adcp/sdk (once, then call binary directly)
388-
# Single install step at the top of the job; subsequent runner
389-
# calls invoke the already-installed binary instead of paying
390-
# the ``npx -y -p ...`` per-invocation extract+link tax.
391-
# Pinned to ADCP_SDK_VERSION (see workflow header) — bump via PR
392-
# so reference-impl breakage from a new SDK release shows up as
393-
# a labelled change set, not silent CI flake.
394-
#
395-
# Vendor missing fixtures into the SDK install:
396-
# ``@adcp/sdk`` does not ship two fixtures its storyboard runner
397-
# needs — ``aao-reference-formats.json`` (AAO format catalog,
398-
# upstream adcontextprotocol/adcp#3307) and
399-
# ``v1-canonical-mapping.json`` (v1→v2 format mapping registry
400-
# at schemas/cache/3.1.0-beta.2/registries/). The SDK's own
401-
# error messages recommend vendoring both at the exact paths
402-
# below. We keep copies under ``tests/fixtures/`` and drop them
403-
# into the SDK's expected locations post-install; idempotent if
404-
# upstream later ships them in the npm tarball.
405-
run: |
406-
npm install -g @adcp/sdk@${ADCP_SDK_VERSION}
407-
adcp --version
408-
SDK_ROOT="$(npm root -g)/@adcp/sdk"
409-
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
410-
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
411-
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
412-
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
413-
414-
- name: Install dependencies
415-
run: |
416-
python -m pip install --upgrade pip
417-
pip install -e ".[dev]"
418-
419-
- name: Start seller agent
420-
run: |
421-
ADCP_PORT=3001 python examples/seller_agent.py &
422-
AGENT_PID=$!
423-
for i in $(seq 1 60); do
424-
# Any HTTP response (including 405 on GET to a POST-only endpoint)
425-
# means the server is up and accepting connections.
426-
# ``||`` runs on the assignment so curl's "000" stdout and the
427-
# fallback don't concatenate when the connection is refused.
428-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 \
429-
http://127.0.0.1:3001/mcp 2>/dev/null) || HTTP_CODE="000"
430-
if [ "$HTTP_CODE" != "000" ]; then
431-
echo "Seller agent ready (HTTP ${HTTP_CODE}, pid ${AGENT_PID})"
432-
break
433-
fi
434-
if ! kill -0 "$AGENT_PID" 2>/dev/null; then
435-
echo "Seller agent process died during startup"
436-
exit 1
437-
fi
438-
if [ "$i" -eq 60 ]; then
439-
echo "Seller agent failed to start within 30s"
440-
kill "$AGENT_PID" 2>/dev/null || true
441-
exit 1
442-
fi
443-
sleep 0.5
444-
done
445-
446-
- name: Run storyboard suite
447-
timeout-minutes: 5
448-
# ``adcp`` was installed once at job start (see "Pre-install"
449-
# step) — call the binary directly to skip per-invocation
450-
# ``npx`` extract+link overhead.
451-
run: |
452-
adcp storyboard run \
453-
http://127.0.0.1:3001/mcp media_buy_seller \
454-
--json --allow-http \
455-
> storyboard-result.json
456-
457-
- name: Assert pass
386+
- name: Run reference seller storyboard harness
387+
timeout-minutes: 15
388+
env:
389+
PYTHON: python
458390
run: |
459-
python -c "
460-
import json, sys, pathlib
461-
p = pathlib.Path('storyboard-result.json')
462-
if not p.exists() or p.stat().st_size == 0:
463-
print('storyboard-result.json missing or empty — runner produced no output')
464-
sys.exit(1)
465-
with p.open() as f:
466-
d = json.load(f)
467-
if d.get('overall_status') != 'passing':
468-
print(json.dumps(d, indent=2))
469-
sys.exit(1)
470-
if not d.get('controller_detected'):
471-
print('controller_detected was false; check DemoStore overrides (see #304)')
472-
sys.exit(1)
473-
"
391+
scripts/ci/run_storyboard_reference_seller.sh
474392
475393
- if: always()
476394
uses: actions/upload-artifact@v7

.github/workflows/docs.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ on:
77
types: [published]
88

99
permissions:
10-
contents: write
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: pages
16+
cancel-in-progress: false
1117

1218
jobs:
1319
docs:
1420
runs-on: ubuntu-latest
21+
environment:
22+
name: github-pages
23+
url: ${{ steps.deployment.outputs.page_url }}
1524
steps:
1625
- uses: actions/checkout@v6
1726

@@ -30,8 +39,16 @@ jobs:
3039
run: |
3140
pdoc --html --output-dir docs/api adcp
3241
42+
- name: Configure Pages
43+
uses: actions/configure-pages@v6
44+
45+
- name: Upload Pages artifact
46+
uses: actions/upload-pages-artifact@v5
47+
with:
48+
path: docs/api/adcp
49+
3350
- name: Deploy to GitHub Pages
34-
uses: peaceiris/actions-gh-pages@v4
51+
id: deployment
52+
uses: actions/deploy-pages@v5
3553
with:
36-
github_token: ${{ secrets.GITHUB_TOKEN }}
37-
publish_dir: docs/api/adcp
54+
artifact_name: github-pages

schemas/cache/3.1.0-beta.3/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,4 +1404,4 @@
14041404
"exercised_tools": []
14051405
}
14061406
}
1407-
}
1407+
}

schemas/patches/03-manifest-signal-owned-discovery-only.patch

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# signal_owned.exercised_tools.
1414
--- a/schemas/cache/3.1.0-beta.3/manifest.json
1515
+++ b/schemas/cache/3.1.0-beta.3/manifest.json
16-
@@ -19,8 +19,7 @@
16+
@@ -20,8 +20,7 @@
1717
"response_schema": "signals/activate-signal-response.json",
1818
"async_response_schemas": [],
1919
"specialisms": [
@@ -22,7 +22,8 @@
2222
+ "signal_marketplace"
2323
]
2424
},
25-
@@ -1389,7 +1388,6 @@
25+
"build_creative": {
26+
@@ -1394,7 +1393,6 @@
2627
"get_signals"
2728
],
2829
"exercised_tools": [
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env bash
2+
# Run the Python reference seller through the AdCP media_buy_seller storyboard.
3+
#
4+
# Required runner input: set exactly one of these modes.
5+
# ADCP_SDK_VERSION=7.10.2 Install published @adcp/sdk from npm.
6+
# ADCP_SDK_TARBALL=/tmp/sdk.tgz Install a candidate @adcp/sdk tarball.
7+
# ADCP_RUNNER_BIN=/path/to/adcp Use a preinstalled storyboard runner binary.
8+
#
9+
# Optional inputs:
10+
# ADCP_PORT=3001
11+
# STORYBOARD_RESULT_PATH=storyboard-result.json
12+
# PYTHON=python3
13+
# ADCP_PYTHON_VENV=.context/storyboard-reference-seller-venv
14+
# ADCP_SDK_ROOT=/path/to/@adcp/sdk
15+
#
16+
# The script assumes it is running from an adcp-client-python checkout,
17+
# installs the Python dependencies needed by examples/seller_agent.py,
18+
# boots the seller, writes the storyboard JSON result, and exits non-zero
19+
# unless overall_status is "passing" and controller_detected is true.
20+
set -euo pipefail
21+
22+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
23+
cd "$ROOT"
24+
25+
ADCP_PORT="${ADCP_PORT:-3001}"
26+
STORYBOARD_RESULT_PATH="${STORYBOARD_RESULT_PATH:-storyboard-result.json}"
27+
PYTHON="${PYTHON:-python3}"
28+
ADCP_PYTHON_VENV="${ADCP_PYTHON_VENV:-$ROOT/.context/storyboard-reference-seller-venv}"
29+
SELLER_LOG_PATH="${SELLER_LOG_PATH:-/tmp/adcp-reference-seller.log}"
30+
31+
ADCP_RUNNER_BIN="${ADCP_RUNNER_BIN:-}"
32+
ADCP_SDK_VERSION="${ADCP_SDK_VERSION:-}"
33+
ADCP_SDK_TARBALL="${ADCP_SDK_TARBALL:-}"
34+
ADCP_SDK_ROOT="${ADCP_SDK_ROOT:-}"
35+
36+
fail() {
37+
echo "ERROR: $*" >&2
38+
exit 1
39+
}
40+
41+
runner_modes=0
42+
[[ -n "$ADCP_RUNNER_BIN" ]] && runner_modes=$((runner_modes + 1))
43+
[[ -n "$ADCP_SDK_TARBALL" ]] && runner_modes=$((runner_modes + 1))
44+
[[ -n "$ADCP_SDK_VERSION" ]] && runner_modes=$((runner_modes + 1))
45+
[[ "$runner_modes" -eq 1 ]] || fail "set exactly one of ADCP_RUNNER_BIN, ADCP_SDK_TARBALL, or ADCP_SDK_VERSION"
46+
47+
require_command() {
48+
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
49+
}
50+
51+
vendor_sdk_fixtures() {
52+
local sdk_root="$1"
53+
54+
if [[ -z "$sdk_root" ]]; then
55+
echo "No @adcp/sdk root detected; skipping SDK fixture vendoring."
56+
return 0
57+
fi
58+
if [[ ! -d "$sdk_root" ]]; then
59+
echo "@adcp/sdk root not found at $sdk_root; skipping SDK fixture vendoring."
60+
return 0
61+
fi
62+
63+
echo "Vendoring storyboard fixtures into $sdk_root"
64+
mkdir -p "$sdk_root/test/lib/v2-projection-fixtures"
65+
cp tests/fixtures/aao-reference-formats.json \
66+
"$sdk_root/test/lib/v2-projection-fixtures/aao-reference-formats.json"
67+
mkdir -p "$sdk_root/schemas/cache/3.1.0-beta.2/registries"
68+
cp tests/fixtures/v1-canonical-mapping.json \
69+
"$sdk_root/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
70+
}
71+
72+
detect_global_sdk_root() {
73+
require_command npm
74+
local npm_root
75+
npm_root="$(npm root -g)"
76+
local sdk_root="$npm_root/@adcp/sdk"
77+
[[ -d "$sdk_root" ]] && printf '%s\n' "$sdk_root"
78+
}
79+
80+
install_or_select_runner() {
81+
if [[ -n "$ADCP_RUNNER_BIN" ]]; then
82+
command -v "$ADCP_RUNNER_BIN" >/dev/null 2>&1 || [[ -x "$ADCP_RUNNER_BIN" ]] || \
83+
fail "ADCP_RUNNER_BIN is not executable or on PATH: $ADCP_RUNNER_BIN"
84+
echo "Using preinstalled storyboard runner: $ADCP_RUNNER_BIN"
85+
if [[ -z "$ADCP_SDK_ROOT" ]] && command -v npm >/dev/null 2>&1; then
86+
ADCP_SDK_ROOT="$(detect_global_sdk_root || true)"
87+
fi
88+
elif [[ -n "$ADCP_SDK_TARBALL" ]]; then
89+
require_command npm
90+
[[ -f "$ADCP_SDK_TARBALL" ]] || fail "ADCP_SDK_TARBALL not found: $ADCP_SDK_TARBALL"
91+
echo "Installing candidate @adcp/sdk tarball: $ADCP_SDK_TARBALL"
92+
npm install -g "$ADCP_SDK_TARBALL"
93+
ADCP_RUNNER_BIN="adcp"
94+
ADCP_SDK_ROOT="${ADCP_SDK_ROOT:-$(detect_global_sdk_root || true)}"
95+
else
96+
require_command npm
97+
echo "Installing published @adcp/sdk@$ADCP_SDK_VERSION"
98+
npm install -g "@adcp/sdk@$ADCP_SDK_VERSION"
99+
ADCP_RUNNER_BIN="adcp"
100+
ADCP_SDK_ROOT="${ADCP_SDK_ROOT:-$(detect_global_sdk_root || true)}"
101+
fi
102+
103+
"$ADCP_RUNNER_BIN" --version
104+
vendor_sdk_fixtures "$ADCP_SDK_ROOT"
105+
}
106+
107+
install_python_dependencies() {
108+
if "$PYTHON" - <<'PY' >/dev/null 2>&1
109+
import sys
110+
raise SystemExit(0 if sys.prefix != sys.base_prefix else 1)
111+
PY
112+
then
113+
echo "Using active Python virtual environment: $PYTHON"
114+
else
115+
if [[ ! -x "$ADCP_PYTHON_VENV/bin/python" ]]; then
116+
echo "Creating Python virtual environment: $ADCP_PYTHON_VENV"
117+
"$PYTHON" -m venv "$ADCP_PYTHON_VENV"
118+
else
119+
echo "Using Python virtual environment: $ADCP_PYTHON_VENV"
120+
fi
121+
PYTHON="$ADCP_PYTHON_VENV/bin/python"
122+
fi
123+
124+
echo "Installing Python dependencies"
125+
"$PYTHON" -m pip install --upgrade pip
126+
"$PYTHON" -m pip install -e ".[dev]"
127+
}
128+
129+
wait_for_seller() {
130+
local pid="$1"
131+
local url="http://127.0.0.1:${ADCP_PORT}/mcp"
132+
133+
for i in $(seq 1 60); do
134+
local http_code
135+
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 "$url" 2>/dev/null) || http_code="000"
136+
if [[ "$http_code" != "000" ]]; then
137+
echo "Seller agent ready (HTTP ${http_code}, pid ${pid})"
138+
return 0
139+
fi
140+
if ! kill -0 "$pid" 2>/dev/null; then
141+
echo "Seller agent process died during startup"
142+
tail -n 80 "$SELLER_LOG_PATH" 2>/dev/null || true
143+
return 1
144+
fi
145+
if [[ "$i" -eq 60 ]]; then
146+
echo "Seller agent failed to start within 30s"
147+
tail -n 80 "$SELLER_LOG_PATH" 2>/dev/null || true
148+
return 1
149+
fi
150+
sleep 0.5
151+
done
152+
}
153+
154+
assert_storyboard_passed() {
155+
"$PYTHON" - "$STORYBOARD_RESULT_PATH" <<'PY'
156+
import json
157+
import pathlib
158+
import sys
159+
160+
path = pathlib.Path(sys.argv[1])
161+
if not path.exists() or path.stat().st_size == 0:
162+
print(f"{path} missing or empty; runner produced no output")
163+
sys.exit(1)
164+
165+
with path.open() as f:
166+
doc = json.load(f)
167+
168+
if doc.get("overall_status") != "passing":
169+
print(json.dumps(doc, indent=2))
170+
sys.exit(1)
171+
172+
if not doc.get("controller_detected"):
173+
print("controller_detected was false; check DemoStore overrides (see #304)")
174+
sys.exit(1)
175+
176+
summary = doc.get("summary") or {}
177+
print("Storyboard passing.")
178+
if summary:
179+
print("summary:", json.dumps(summary, sort_keys=True))
180+
PY
181+
}
182+
183+
cleanup() {
184+
if [[ -n "${SELLER_PID:-}" ]]; then
185+
kill "$SELLER_PID" 2>/dev/null || true
186+
wait "$SELLER_PID" 2>/dev/null || true
187+
fi
188+
}
189+
trap cleanup EXIT
190+
191+
install_or_select_runner
192+
install_python_dependencies
193+
194+
mkdir -p "$(dirname "$STORYBOARD_RESULT_PATH")"
195+
echo "Starting examples/seller_agent.py on port $ADCP_PORT"
196+
ADCP_PORT="$ADCP_PORT" "$PYTHON" examples/seller_agent.py >"$SELLER_LOG_PATH" 2>&1 &
197+
SELLER_PID=$!
198+
wait_for_seller "$SELLER_PID"
199+
200+
echo "Running media_buy_seller storyboard"
201+
set +e
202+
"$ADCP_RUNNER_BIN" storyboard run \
203+
"http://127.0.0.1:${ADCP_PORT}/mcp" media_buy_seller \
204+
--json --allow-http \
205+
>"$STORYBOARD_RESULT_PATH"
206+
RUNNER_STATUS=$?
207+
set -e
208+
209+
if [[ "$RUNNER_STATUS" -ne 0 ]]; then
210+
echo "Storyboard runner exited with status $RUNNER_STATUS"
211+
[[ -s "$STORYBOARD_RESULT_PATH" ]] && cat "$STORYBOARD_RESULT_PATH"
212+
exit "$RUNNER_STATUS"
213+
fi
214+
215+
assert_storyboard_passed

0 commit comments

Comments
 (0)