-
Notifications
You must be signed in to change notification settings - Fork 1
231 lines (212 loc) · 11 KB
/
Copy pathcloud-runtime-artifact.yml
File metadata and controls
231 lines (212 loc) · 11 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
name: Relayflow v2 Cloud runtime artifact
on:
workflow_dispatch:
# Also on push to main, because a PR is checked at its own head and never as
# merged. Every PR squash-merges onto a main that has moved since its CI ran,
# so the composed result was going unverified: on 2026-09-05, twelve merges
# landed in one evening across the exactly-once claim path, resume adoption
# and the authored-flow executor -- each green on its own branch, none of them
# ever run together until this was dispatched by hand.
#
# A broken compose would otherwise surface as an unrelated PR going red, which
# is the most expensive way to find it: the author debugs their own change
# first. Paths are deliberately NOT filtered here -- on main the question is
# whether the tree is good, not whether this commit touched the kernel.
push:
branches:
- main
pull_request:
paths:
- ".github/workflows/cloud-runtime-artifact.yml"
- "kernel/**"
- "packages/sdk/**"
- "scripts/cloud-artifact.mjs"
- "scripts/build-standalone-cli.mjs"
- "scripts/cloud-artifact.test.mjs"
- "testdata/**"
permissions:
contents: read
jobs:
linux-x64-artifact:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
# PR workflows otherwise build GitHub's ephemeral merge ref and stamp
# a provenance SHA that can disappear after the PR closes.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@stable
- name: Test artifact contract
run: node --test scripts/cloud-artifact.test.mjs
- name: Build relayflowd
working-directory: kernel
run: cargo build --locked --release -p relayflowd
# The kernel suite had never run in CI. The only cargo invocation was the
# release build above, so every kernel-side defect was invisible here by
# construction -- including, on 2026-09-03, an exactly-once double-fire
# where one effect fired twice, a `$ref` cycle that aborted the daemon and
# re-ran the effect on every resume, and two tests in the tree encoding
# opposite contracts that both passed because neither was ever executed.
#
# The toolchain and the build are already paid for above; this adds the
# test run and nothing else.
#
# Plain `cargo`, NOT ops/cargo.sh. That wrapper unconditionally redirects
# RUSTUP_HOME to $HOME/.relayflows-toolchain/rustup, which on a runner is
# empty, so the rustup shim has nothing to choose:
# error: rustup could not choose a version of cargo to run, because one
# wasn't specified explicitly, and no default is configured
# The wrapper exists for a cloud sandbox, where the propagated tree drops
# files over a size cap; a GitHub runner has neither that constraint nor
# that tree, and dtolnay/rust-toolchain has already installed a default
# toolchain into the standard home. Matching the build step above is both
# simpler and the configuration that is known to work here.
- name: Test kernel
working-directory: kernel
run: cargo test --workspace
- name: Build authoring surface
working-directory: packages/surface
run: |
bun install --frozen-lockfile --ignore-scripts
bun run build
# --ignore-scripts because the surface is already built above; without it
# npm runs the file: dependency's prepare before its own devDependencies
# exist. The SDK's own build is the next step, so nothing is skipped.
- name: Install SDK dependencies
run: npm ci --prefix packages/sdk --ignore-scripts
# `npm ci` above installs `@relayflows/surface@2.0.8` from the REGISTRY,
# not the local one built two steps up. Any SDK change that imports a
# symbol that landed in the local surface but is not yet in the published
# 2.0.8 (e.g. LlmOptions on flows#296) then fails typecheck. Override the
# registry copy with the local directory install — --no-save keeps
# package.json / package-lock.json unchanged so this is a CI-only local
# override.
#
# The `./` prefix is load-bearing: `npm install packages/surface`
# without it makes npm treat the string as a GitHub org/repo shorthand
# (ssh://git@github.com/packages/surface.git) which then fails
# authentication.
- name: Override registry surface with local build
run: npm install ./packages/surface --prefix packages/sdk --no-save --ignore-scripts
# `workflows/*.flow.ts` import `@relayflows/surface` from the repo root,
# where Node's resolution finds no node_modules at all in CI: everything
# above installs under packages/sdk. A workflow-driving test (e.g.
# tests/stuck-run-triage.test.ts) therefore cannot load its subject.
# Link the same local surface at the root, which is also what a checkout
# needs for `flows check` to run against a workflow file.
- name: Link the local surface at the repo root
run: |
mkdir -p node_modules/@relayflows
ln -sfn ../../packages/sdk/node_modules/@relayflows/surface \
node_modules/@relayflows/surface
node -e "console.log(require.resolve('@relayflows/surface'))"
- name: Provision hosted extension sandbox
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends bubblewrap
# Ubuntu 24.04 restricts unprivileged user namespaces through
# AppArmor by default. This runner is an ephemeral VM dedicated to
# the job; enable the kernel facility so the exact production
# user/PID/network/mount namespace command can be exercised.
if sysctl kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
/usr/bin/bwrap --version
/usr/bin/bwrap --unshare-all --die-with-parent --new-session \
--ro-bind / / /bin/true
- name: Test SDK and type-level authoring contracts
working-directory: packages/sdk
env:
# `live-kernel.test.ts` runs one case against the REAL Claude analyzer
# and fails by default when it cannot, deliberately: an unavailable
# analyzer is diagnostics, never acceptance, and a reader must not get
# a green that proves nothing about gate 2.
#
# A GitHub runner is exactly the case the flag was written for -- it
# has no `claude` binary and no model access:
# LIVE_ANALYZER_UNAVAILABLE: ... cannot run "claude": spawnSync claude ENOENT
# so without this the full SDK suite can never pass here, which is a
# defect in #153: it enabled the suite without giving CI a way to run
# it.
#
# Setting it is an explicit statement, not a convenience: **this
# workflow is not gate-2 acceptance evidence.** Gate-2 evidence has to
# come from a machine that can actually reach a model, and the skipped
# case says so in its own output. Everything else in the suite still
# runs and still gates.
RELAYFLOWS_ALLOW_ANALYZER_SKIP: '1'
run: |
# The whole suite, not four named files. Naming files means a test
# added to any other file never runs, which is how ~22 of ~26 SDK
# test files were uncovered.
#
# This is `npm test` expanded, minus test:prep. `npm test` is
# test:prep && typecheck && build && typecheck:tests && vitest run,
# and test:prep shells out to ops/cargo.sh -- which fails on a runner
# for the reason given on the kernel step above. Its only purpose is
# to produce the relayflowd binary that tests/live-kernel.test.ts
# execs, and this job has already built one: the release binary from
# the build step. Point RELAYFLOWD_BIN at it rather than compiling a
# second, debug copy through a wrapper that cannot run here.
#
# The build is still required: several test files fail at collection
# without packages/sdk/dist, which is why a bare `vitest run` is not enough.
# test:prep's other half, kept: it re-asserts the executable bit on the
# preflight CLI fixtures. They are committed 100755 so actions/checkout
# already restores them, but the guard is one line and the failure it
# prevents is an opaque EACCES deep inside a preflight test.
( [ ! -d ../testdata/preflight ] || \
find ../testdata/preflight -name '*-cli' -type f -exec chmod +x {} + )
npm run typecheck
npm run build
npm run typecheck:tests
RELAYFLOWD_BIN="$GITHUB_WORKSPACE/kernel/target/release/relayflowd" \
./node_modules/.bin/vitest run
- name: Build standalone flows CLI
run: |
mkdir -p dist/cloud-artifact-input
node scripts/build-standalone-cli.mjs bun-linux-x64 dist/cloud-artifact-input/flows
- name: Assemble artifact and smoke verifier path
env:
SOURCE_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
node scripts/cloud-artifact.mjs build \
--relayflowd kernel/target/release/relayflowd \
--flows-executable dist/cloud-artifact-input/flows \
--output-dir dist/cloud-artifact \
--source-commit "$SOURCE_COMMIT"
archive="$(find dist/cloud-artifact -name '*.tar.gz' -type f -print -quit)"
checksum="$(awk '{print $1}' "$archive.sha256")"
node scripts/cloud-artifact.mjs verify \
--archive "$archive" \
--sha256 "$checksum"
- name: Smoke exact Linux artifact
run: |
archive="$(find dist/cloud-artifact -name '*.tar.gz' -type f -print -quit)"
mkdir -p dist/cloud-artifact-smoke
tar -xzf "$archive" -C dist/cloud-artifact-smoke
dist/cloud-artifact-smoke/bin/relayflowd --help
flows_output="$(dist/cloud-artifact-smoke/bin/flows check --json testdata/hello-deterministic.flow.yaml)"
printf '%s\n' "$flows_output"
node -e '
const report = JSON.parse(process.argv[1]);
if (report.ok !== true) throw new Error("flows smoke report was not ok");
if (report.path !== "testdata/hello-deterministic.flow.yaml") {
throw new Error(`flows smoke reported unexpected path: ${report.path}`);
}
' "$flows_output"
- uses: actions/upload-artifact@v4
with:
name: relayflow-v2-linux-x64-${{ github.event.pull_request.head.sha || github.sha }}
path: |
dist/cloud-artifact/*.tar.gz
dist/cloud-artifact/*.sha256
if-no-files-found: error
retention-days: 14