Repository navigation
156 lines (145 loc) · 8.58 KB
/
Copy pathdeploy-api.yml
File metadata and controls
156 lines (145 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
name: Deploy API Worker
on:
push:
branches: [main]
paths:
- 'workers/api/**'
- 'packages/sdk/**'
- '.github/workflows/deploy-api.yml'
# The build chain moved OUT of this file into a composite action (#779). Without this
# entry an edit to the shared chain would trigger no deploy at all — the duplication it
# replaced was at least watched, because it lived in the path above.
- '.github/actions/build-platform/**'
# The deploy itself runs through these (#1046): a change to the retry policy or the
# trigger check must ship through the deploy it governs.
- 'scripts/deploy-worker.mjs'
- 'scripts/lib/deploy-worker.mjs'
- 'scripts/verify-api-triggers.mjs'
- 'scripts/lib/verify-triggers.mjs'
workflow_dispatch: {}
concurrency:
group: deploy-api
cancel-in-progress: true
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# `pnpm test` below now includes migration-hygiene.test.ts, which shells out to
# check-migrations.mjs --require-history. That guard REFUSES a shallow clone by
# design: a history-dependent check with no history passes by verifying nothing,
# so it fails loudly instead. Depth 1 therefore red-lined this deploy from the
# commit the guard landed in (48eb443) — the API fixes were merged and undeployed
# while Deploy Host stayed green, so the console looked fine and the worker was stale.
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
# The install + build chain shared with deploy-host.yml and (still hand-rolled) ci.yml.
# Must come AFTER pnpm/action-setup and setup-node above — the composite shells out to
# `pnpm` directly and has no setup of its own.
#
# Why an API deploy builds the CONSOLE and the HOST pages at all: `pnpm test` below runs
# the WHOLE suite. It imports @proagentstore/sdk through its exports map (its built dist),
# and it collects workers/host/src/admin-api-proxy.test.ts, which imports the host worker's
# index.ts, which imports ./pages.js at module load. `pages.ts` is build output that
# build.js inlines store/ into, and build.js reads the console and admin Vite bundles and
# the generated store/docs. Miss any link and the deploy stops for a reason unrelated to
# the API — which is exactly what happened when this chain was hand-copied without the
# docs steps: run 34004410070 died at `node build.js` with "No docs at store/docs", fixed
# in 0ca213b3. Extracting the chain (#779) is what stops the copy existing to go stale.
- uses: ./.github/actions/build-platform
- name: Run tests
run: pnpm test
- name: Apply D1 migrations
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: workers/api
command: d1 migrations apply pags --remote
- name: Deploy API worker
id: deploy
# Retried on Cloudflare's own transient API failures only, up to 3 attempts (#1046):
# four of six deploys on 2026-10-12 uploaded the Worker and then failed on ONE workflow
# trigger or a malformed API response. Anything else — auth, validation, build — fails at
# once as before. The step ends in one status (deployed | deployed_after_retry | failed |
# retryable_failure), on the summary and as `steps.deploy.outputs.status`; only the first
# two are green.
#
# --var API_BUILD overrides the wrangler.toml "dev" default with the real 12-char git SHA
# (#735) so every server-side error_log row carries the build that wrote it. 12 chars
# matches the client-side convention from #539 and is collision-free in practice (the
# birthday bound is ~2^48, orders of magnitude beyond any realistic repo history).
run: |
SHORT_SHA=$(git rev-parse --short=12 HEAD)
node scripts/deploy-worker.mjs --cwd workers/api -- --var "API_BUILD:${SHORT_SHA}"
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Verify triggers
# The release's final trigger state, read back from Cloudflare (#1046): every [[workflows]]
# entry in wrangler.toml must exist and be bound to proagentstore-api and its class, and the
# crons must match. Runs after a FAILED deploy too — the Worker may be live with one trigger
# missing (bb7c11e0: pags-job-apply), and this table is what says which part shipped. A
# mismatch, or a read that keeps failing, is red.
if: ${{ !cancelled() && steps.deploy.outcome != 'skipped' }}
run: node scripts/verify-api-triggers.mjs --cwd workers/api
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Smoke test
# Still runs when only the trigger check above is red: the Worker is deployed, and its
# health is a separate fact the run should report (#1046).
if: ${{ !cancelled() && steps.deploy.outcome == 'success' }}
# The admin perimeter's mode is asserted, not just the 200 (#108 C4). `/health` reports
# `adminPerimeter` = off | audit | enforce, derived from Worker SECRETS — which live outside
# this repo, so a `wrangler secret delete`, a typo'd value or a deploy that lost them turns
# the Cloudflare Access gate back to `off` with every automated signal still green. `off`
# is indistinguishable from healthy unless something says what it is SUPPOSED to be.
#
# EXPECTED_ADMIN_PERIMETER is a repo VARIABLE (Settings → Secrets and variables → Actions →
# Variables), defaulting to `off`, which is what production runs today. It is moved by
# hand, in step with the rollout in docs/admin-access-perimeter.md: set it to `audit` when
# CF_ACCESS_TEAM_DOMAIN + CF_ACCESS_AUD go in, and to `enforce` with CF_ACCESS_ENFORCE.
# The worker is ALREADY deployed when this runs, so a mismatch does not roll anything
# back — it makes the drift a red deploy instead of a silently re-opened admin surface.
#
# The public catalogue is checked separately from `/health` (#830). A Worker can be
# reachable while its D1-backed catalogue query is broken; MCP's `list_agents` depends
# on this endpoint, and a coding template must remain subscribable through it.
env:
EXPECTED_ADMIN_PERIMETER: ${{ vars.EXPECTED_ADMIN_PERIMETER || 'off' }}
run: |
for i in 1 2 3 4 5; do
STATUS=$(curl -sS -o /tmp/health.json -w "%{http_code}" https://api.proagentstore.online/health)
if [ "$STATUS" = "200" ]; then
echo "Health check passed"
ACTUAL=$(jq -r '.adminPerimeter // "absent"' /tmp/health.json)
if [ "$ACTUAL" != "$EXPECTED_ADMIN_PERIMETER" ]; then
echo "::error::Admin perimeter is '$ACTUAL' but EXPECTED_ADMIN_PERIMETER is '$EXPECTED_ADMIN_PERIMETER'."
echo "If the CF_ACCESS_* secrets were changed on purpose, update the EXPECTED_ADMIN_PERIMETER repo variable to match."
echo "If they were not, the Cloudflare Access gate on /v1/admin/* has drifted — see docs/admin-access-perimeter.md."
exit 1
fi
echo "Admin perimeter is '$ACTUAL', as expected"
CATALOGUE_STATUS=$(curl -sS -o /tmp/agents.json -w "%{http_code}" "https://api.proagentstore.online/v1/agents?limit=500")
if [ "$CATALOGUE_STATUS" = "200" ] && jq -e '(.agents | type == "array") and any(.agents[]?; .slug == "coder" or .slug == "coder-repo" or .slug == "tmux-coder")' /tmp/agents.json > /dev/null; then
echo "Public catalogue contains a coding agent"
exit 0
fi
echo "Attempt $i: public catalogue returned $CATALOGUE_STATUS or no coding agent, retrying..."
else
echo "Attempt $i: health check got $STATUS, retrying..."
fi
sleep 5
done
echo "Smoke test failed after 5 attempts: /health must return 200 with the expected admin perimeter, and /v1/agents must return a coding agent."
exit 1