-
Notifications
You must be signed in to change notification settings - Fork 0
257 lines (233 loc) · 10.9 KB
/
Copy pathci.yml
File metadata and controls
257 lines (233 loc) · 10.9 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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
name: CI
# Branches are covered by `pull_request`; `main` by `push`. Restricting push to
# main is what stops a branch with an open PR producing two runs of this
# workflow -- the same approach the installed docs-ci.yml takes, and the reason
# it needs no concurrency group either.
#
# An earlier version triggered on every push *and* pull_request, deduplicated by
# a concurrency group with cancel-in-progress. That raced: whichever run started
# second cancelled the first, leaving two check-runs named `engine` on one commit
# -- one success, one cancelled -- and a required-status-check rule that sees the
# cancelled one blocks the merge even though everything passed.
#
# No `paths` filter on either trigger. A path-filtered `pull_request` was tried and
# reverted: `engine` is a required status check on `main`'s ruleset, and a workflow
# run that never starts because no matching path changed means the required check
# never reports at all -- GitHub then leaves the PR waiting on a check that will
# never arrive, rather than treating it as satisfied. The `engine` job itself now
# does the equivalent skip internally (see its own "Determine..." step below),
# which keeps the job always reporting while still skipping the expensive steps on
# a documentation-only PR (07-replay.md §8's "never a merge gate on
# documentation-only changes").
"on":
push:
branches:
- main
tags:
- "v*"
pull_request:
permissions:
contents: read
# Only reachable for pushes to main now, since a PR branch produces a single
# run. cancel-in-progress is still right there: consecutive pushes to main
# supersede each other.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
engine:
name: engine
# Always runs, on every event including a tag push -- a tag can in principle be cut
# against a commit that was never itself verified as a branch push or a checked PR
# (an unenforced assumption an earlier version of this job made), so this is the one
# place every ref actually gets typecheck/lint/test.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
# pull_request needs both endpoints reachable to diff against; push (branch or
# tag) always runs the real steps below regardless, so shallow is fine there.
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
- name: Determine whether the engine package changed
id: changed
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
if git diff --quiet "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" -- src/engine; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Set up Node.js
if: steps.changed.outputs.changed == 'true'
uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: src/engine/package-lock.json
- name: Install dependencies
if: steps.changed.outputs.changed == 'true'
working-directory: src/engine
run: npm ci
# verification: true
- name: Typecheck
if: steps.changed.outputs.changed == 'true'
working-directory: src/engine
run: npm run typecheck
# verification: true
- name: Lint
if: steps.changed.outputs.changed == 'true'
working-directory: src/engine
run: npm run lint
# verification: true
- name: Test
if: steps.changed.outputs.changed == 'true'
working-directory: src/engine
run: npm test
# verification: true
- name: Pack package
if: steps.changed.outputs.changed == 'true'
id: pack-package
working-directory: src/engine
run: |
tarball="$(npm pack --silent)"
echo "tarball=$tarball" >> "$GITHUB_OUTPUT"
# verification: true
- name: Inspect tarball
if: steps.changed.outputs.changed == 'true'
working-directory: src/engine
env:
TARBALL: ${{ steps.pack-package.outputs.tarball }}
run: |
if [ -z "$TARBALL" ]; then
echo "package step did not produce a tarball path"
exit 1
fi
tarball_path="$TARBALL"
if [ ! -f "$tarball_path" ]; then
echo "package tarball not found: $tarball_path"
exit 1
fi
entries="$(tar -tzf "$tarball_path")"
if echo "$entries" | grep -qE '(^|/)src/'; then
echo "tarball contains source files under src/"
exit 1
fi
if echo "$entries" | grep -qE '(^|/)tsconfig[^/]*\\.json$'; then
echo "tarball contains tsconfig JSON files"
exit 1
fi
if echo "$entries" | grep -qE '\\.test\\.(js|mjs|ts|c?js|d\\.ts|js\\.map|mjs\\.map|ts\\.map)$'; then
echo "tarball contains test build artifacts"
exit 1
fi
if ! echo "$entries" | grep -q '^package/dist/'; then
echo "tarball does not contain dist output"
exit 1
fi
# verification: true
- name: Consumer smoke
if: steps.changed.outputs.changed == 'true'
run: |
package_tgz="${{ steps.pack-package.outputs.tarball }}"
if [ -z "$package_tgz" ]; then
echo "No engine package tarball available for smoke install"
exit 1
fi
rm -rf consumer-smoke/node_modules consumer-smoke/dist
(cd consumer-smoke && npm ci)
(cd consumer-smoke && npm run install:engine)
(cd consumer-smoke && npm run build && npm run smoke)
# The replay regression oracle's cross-version comparison (07-replay.md §8): on every
# release tag, run *this* tag's engine against the *previous* tag's own fixture
# submissions and committed .outcome.json files together — the actual "did this change
# alter a game that already exists" question, distinct from the engine job's own test
# run above, which only ever compares a build against its own commit's corpus
# (07-replay.md §1).
#
# Both the fixture and the outcome are pulled from the previous tag, not just the
# outcome — comparing this commit's (possibly edited or removed) fixture inputs against
# an old recorded outcome would not be a clean version-to-version comparison.
#
# Skips cleanly, not a failure, when there is no previous tag yet (plans/27, milestone
# M3) — the very first tag has nothing to compare against.
release-tag-replay:
name: replay oracle — release tag comparison
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository (full history, so previous tags resolve)
uses: actions/checkout@v7
with:
fetch-depth: 0
# actions/checkout defaults fetch-tags to false. On a tag-triggered run that means
# only the triggering tag itself is fetched -- fetch-depth: 0 gets full commit
# history, but not sibling tags -- so "Find the previous release tag" below always
# finds none. Found live: pushing v0.2.0 produced "No previous tag before v0.2.0"
# even though v0.1.0 was on origin the whole time (run 30698926200).
fetch-tags: true
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: src/engine/package-lock.json
- name: Install dependencies
working-directory: src/engine
run: npm ci
- name: Find the previous release tag
id: previous
run: |
current="${GITHUB_REF#refs/tags/}"
previous=$(git tag --list 'v*' --sort=-v:refname | grep -A1 -F -x "$current" | tail -n1)
if [ -z "$previous" ] || [ "$previous" = "$current" ]; then
echo "No previous tag before $current — nothing to compare yet (M3, plans/27)."
echo "found=false" >> "$GITHUB_OUTPUT"
else
echo "Comparing $current against $previous"
echo "found=true" >> "$GITHUB_OUTPUT"
echo "tag=$previous" >> "$GITHUB_OUTPUT"
fi
# Runs from the repo root (no working-directory override), matching the pathspec
# below, which is itself repo-root-relative -- src/engine/fixtures/replay. An
# earlier version set working-directory: src/engine here while keeping the
# src/engine/-prefixed pathspec, which resolved to the nonexistent
# src/engine/src/engine/fixtures/replay and silently extracted nothing.
#
# `|| true` on the ls-tree/grep pipeline: an empty match (the previous tag predates
# W22's corpus entirely -- true of v0.1.0, the only tag today, cut before any fixture
# existed) must not itself end the step under this shell's `-eo pipefail` default.
# The `has_fixtures` check below is the real guard against that case; this is cheap
# insurance alongside it.
- name: Extract the previous tag's fixtures and outcomes
id: extract
if: steps.previous.outputs.found == 'true'
run: |
mkdir -p /tmp/replay-baseline
for path in $(git ls-tree -r --name-only "${{ steps.previous.outputs.tag }}" -- src/engine/fixtures/replay | grep -E '\.(fixture|outcome)\.json$' || true); do
name=$(basename "$path")
git show "${{ steps.previous.outputs.tag }}:$path" > "/tmp/replay-baseline/$name"
done
if ls /tmp/replay-baseline/*.fixture.json >/dev/null 2>&1; then
echo "has_fixtures=true" >> "$GITHUB_OUTPUT"
else
echo "${{ steps.previous.outputs.tag }} predates the replay corpus (W22) — nothing to compare yet."
echo "has_fixtures=false" >> "$GITHUB_OUTPUT"
fi
# stable-life.replay.test.ts (simulation kind, W40) and
# world-graph-mvp.replay.test.ts (world-graph kind, W49) join bureaucracy's own suite
# here -- all read from the same REPLAY_BASELINE_DIR, and all degrade to zero
# test cases rather than a failure when the baseline tag predates their own corpus
# (has_fixtures above only guards "the corpus exists at all", not "every kind's own
# slice of it exists yet" -- v0.1.0-to-just-after-W40 is exactly that transition).
- name: Run the corpus against the previous tag's fixtures and outcomes
if: steps.previous.outputs.found == 'true' && steps.extract.outputs.has_fixtures == 'true'
working-directory: src/engine
env:
REPLAY_BASELINE_DIR: /tmp/replay-baseline
run: npx vitest run src/campaigns/bulgaria-bureaucracy.replay.test.ts src/campaigns/stable-life.replay.test.ts src/campaigns/world-graph-mvp.replay.test.ts