a73x

1cb92020

build(ci): the console's types answer to the gate

a73x   2026-08-11 11:55

Commit message
build(ci): the console's types answer to the gate

Two branches gated green and merged red. One added fleet.test.ts, whose typed
fixtures set every field of a VM as the API describes it; the other dropped
`persistent` from the schema, and so from the generated api-types.ts. Neither
touched a line the other touched, so git merged them without a word, and the
merged tree did not typecheck: a fixture was still setting a field the server
no longer sends. Nothing noticed, because `make ci` ran web-test and not check.

vitest cannot notice. It compiles each module through vite's pipeline, which
strips types rather than checking them — the fixture ran, the assertions
passed, and 24 tests were green on a tree where `npm run check` reported an
error. That is the whole bet the typed fixtures make: api-types.ts is the only
description of the wire the console has, and a fixture that contradicts it is a
type error and nothing else. The bet only pays if something checks types where
the drift happens, which is the merge.

So web-check joins `make ci`, in web-test's idiom: skips with a note when
web/node_modules/.bin/svelte-check is absent, so `make ci` still runs on a
machine with no Node, and CI runs `make web` first, so it is enforced there.
`npm run check` runs `svelte-kit sync` ahead of svelte-check, which is what
lets it pass in a bare worktree — .svelte-kit is gitignored, and generating it
is part of the check rather than a prerequisite the caller has to remember.

Reproduced both ways before landing: with the dropped field put back into the
fixture, web-test passes and `make ci` exits 2 at web-check; with the toolchain
absent, web-check prints its skip and exits 0.

Makefile
Old New
@@ -12,7 +12,7 @@ LINT_WARN := errcheck,revive,gocyclo,funlen,gocritic,misspell,unconvert,nakedret
12 12
13 .PHONY: build build-go build-darwin web test vet proto clean \ 13 .PHONY: build build-go build-darwin web test vet proto clean \
14 lint lint-extra arch cover fmt fmt-check tidy-check proto-check shape shape-check api api-check \ 14 lint lint-extra arch cover fmt fmt-check tidy-check proto-check shape shape-check api api-check \
15 site site-check ci deadcode web-test \ 15 site site-check ci deadcode web-test web-check \
16 deploy release ship hooks site-image server-image 16 deploy release ship hooks site-image server-image
17 17
18 # Enable the repo's client-side merge gate: point git at .githooks, whose 18 # Enable the repo's client-side merge gate: point git at .githooks, whose
@@ -209,6 +209,23 @@ web-test:
209 echo "web-test: vitest not installed (run 'make web') — SKIPPING (enforced in CI)"; \ 209 echo "web-test: vitest not installed (run 'make web') — SKIPPING (enforced in CI)"; \
210 fi 210 fi
211 211
212 # Console types under test (svelte-check over the SPA and its fixtures). This is
213 # where wire drift surfaces: the api-types.ts the generator writes is the only
214 # description of the API the console has, and a fixture that still sets a field
215 # the server stopped sending is a type error and nothing else. vitest compiles
216 # each module in isolation and will not see it.
217 #
218 # `npm run check` runs `svelte-kit sync` first, so a bare worktree with no
219 # .svelte-kit generates it here rather than failing on missing $$types.
220 #
221 # Skips (does not fail) when the web toolchain is absent, like web-test.
222 web-check:
223 @if [ -x web/node_modules/.bin/svelte-check ]; then \
224 cd web && npm run check; \
225 else \
226 echo "web-check: svelte-check not installed (run 'make web') — SKIPPING (enforced in CI)"; \
227 fi
228
212 # Whole-program dead-code gate: fails on any function unreachable from a real 229 # Whole-program dead-code gate: fails on any function unreachable from a real
213 # entrypoint — every main() in cmd/. Rooting at the binaries (NOT -test) is what 230 # entrypoint — every main() in cmd/. Rooting at the binaries (NOT -test) is what
214 # catches production code kept alive only by its own tests; the fix is to remove 231 # catches production code kept alive only by its own tests; the fix is to remove
@@ -240,7 +257,7 @@ deadcode:
240 # The merge gate. Mirrors the required checks in CI. `test` is the authoritative 257 # The merge gate. Mirrors the required checks in CI. `test` is the authoritative
241 # race-detector run; `cover` re-runs without -race to enforce the ratchet; `arch` 258 # race-detector run; `cover` re-runs without -race to enforce the ratchet; `arch`
242 # re-runs the fitness tests with -count=1 (the race run may serve them cached). 259 # re-runs the fitness tests with -count=1 (the race run may serve them cached).
243 ci: vet build-go build-darwin arch lint fmt-check test cover tidy-check proto-check api-check shape-check deadcode site-check web-test 260 ci: vet build-go build-darwin arch lint fmt-check test cover tidy-check proto-check api-check shape-check deadcode site-check web-test web-check
244 261
245 # Compile every Go package (no Node/web build needed — the embed dir ships a 262 # Compile every Go package (no Node/web build needed — the embed dir ships a
246 # placeholder, so the server builds and serves a "UI not built" notice). 263 # placeholder, so the server builds and serves a "UI not built" notice).