Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -748,3 +748,145 @@ jobs:
tenant-a-storyboard.json
tenant-b-storyboard.json
if-no-files-found: warn

storyboard-sales-proposal-mode:
name: AdCP storyboard runner — sales-proposal-mode (proposal_finalize)
runs-on: ubuntu-latest
# v1.5 ProposalManager finalize lifecycle proof. The mock seller
# declares ``finalize=True`` + wires an ``InMemoryProposalStore``;
# the framework's dispatch wiring intercepts ``refine[i].action='finalize'``
# requests, runs ``finalize_proposal``, commits via the store, and
# auto-hydrates ``ctx.recipes`` on subsequent ``create_media_buy``
# calls. This job is the storyboard-level proof that the design
# works end-to-end. Blocking gate — no continue-on-error.
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: Cache ~/.npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-adcp-sdk
restore-keys: |
${{ runner.os }}-npm-

- name: Pre-install @adcp/sdk
run: |
npm install -g @adcp/sdk@latest
adcp --version

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Boot sales-proposal-mode seller
run: |
ADCP_PORT=3003 python -m examples.sales_proposal_mode_seller.src.app &
SELLER_PID=$!
echo "SELLER_PID=$SELLER_PID" >> "$GITHUB_ENV"
for i in $(seq 1 60); do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 1 \
http://127.0.0.1:3003/mcp 2>/dev/null) || HTTP_CODE="000"
if [ "$HTTP_CODE" != "000" ]; then
echo "Seller ready (HTTP ${HTTP_CODE}, pid ${SELLER_PID})"
break
fi
if ! kill -0 "$SELLER_PID" 2>/dev/null; then
echo "Seller process died during startup"
exit 1
fi
if [ "$i" -eq 60 ]; then
echo "Seller failed to start within 30s"
kill "$SELLER_PID" 2>/dev/null || true
exit 1
fi
sleep 0.5
done

- name: Run storyboard — proposal_finalize
timeout-minutes: 5
# Targeted run of the v1.5-relevant scenario. Setup +
# brief_with_proposals must pass — those exercise the framework's
# finalize-interception seam and draft-persistence wiring. Later
# phases (refine / finalize / accept) chain through stateful
# state that the storyboard runner seeds via sync_accounts;
# broader stateful-chain support is orthogonal v1.5 work.
run: |
adcp storyboard run \
http://127.0.0.1:3003/mcp media_buy_seller/proposal_finalize \
--json --allow-http \
> proposal-finalize-storyboard.json
cat proposal-finalize-storyboard.json | head -200

- name: Assert v1.5 dispatch path scenarios pass
run: |
python -c "
import json, sys, pathlib
p = pathlib.Path('proposal-finalize-storyboard.json')
if not p.exists() or p.stat().st_size == 0:
print('storyboard result missing or empty')
sys.exit(1)
with p.open() as f:
d = json.load(f)
# Assert the v1.5 dispatch-path scenarios that exercise the
# framework's intercept seam pass. The seam runs in:
# - proposal_finalize/setup (sync_accounts is skip-tolerant)
# - proposal_finalize/brief_with_proposals (manager.get_products
# round-trip + framework draft persistence)
required_passing = {
'media_buy_seller/proposal_finalize/setup',
'media_buy_seller/proposal_finalize/brief_with_proposals',
}
passed = set()
for track in d.get('tracks', []) or []:
for s in track.get('scenarios', []) or []:
if s.get('overall_passed'):
passed.add(s.get('scenario'))
missing = required_passing - passed
if missing:
# Per-scenario likely-cause hints. A contributor breaks the
# framework finalize wiring; CI tells them what to look at
# rather than just naming a scenario.
hints = {
'media_buy_seller/proposal_finalize/setup': (
'sync_accounts dispatch failed. The seam is in '
'src/adcp/decisioning/handler.py and '
'src/adcp/decisioning/proposal_dispatch.py. See '
'docs/proposals/proposal-manager-v15-design.md § D5.'
),
'media_buy_seller/proposal_finalize/brief_with_proposals': (
'Manager.get_products + framework draft persistence '
'broke. Check maybe_persist_draft_after_get_products '
'in src/adcp/decisioning/proposal_dispatch.py. See '
'docs/proposals/proposal-manager-v15-design.md § D1.'
),
}
print('FAIL: required scenarios did not pass:')
for s in sorted(missing):
hint = hints.get(s, 'no hint registered for this scenario')
print(f' - {s}')
print(f' likely cause: {hint}')
print()
print('--- raw storyboard result ---')
print(json.dumps(d, indent=2))
sys.exit(1)
print('PASS: v1.5 dispatch-path scenarios pass')
"

- if: always()
uses: actions/upload-artifact@v4
with:
name: sales-proposal-mode-storyboard-${{ github.run_attempt }}
path: proposal-finalize-storyboard.json
if-no-files-found: warn
Loading
Loading