Packaged Bun runtime #167
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Packaged Bun runtime | |
| # Reusable, with TWO callers and no trigger of its own: | |
| # - windows-proof-main.yml — post-merge on `main`, scoped by WINDOWS_SCOPE_PATHS | |
| # - release.yml — unconditionally, in front of every job that publishes | |
| # It briefly gated pull requests too; that cost +4m47 on every in-scope PR and was | |
| # reversed. The 45-entry `on.pull_request.paths` filter that used to live here lives in | |
| # WINDOWS_SCOPE_PATHS (src/shared/windows-ci-scope.ts), so one list serves both callers. | |
| # See decisions/2026/08/06/windows-proof-post-merge-not-pull-request.md. | |
| on: | |
| workflow_call: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| package-runtime: | |
| strategy: | |
| matrix: | |
| os: [windows-latest, macos-latest, ubuntu-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup build Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.14" | |
| # Windows only, and the asymmetry is the point: the same command in the same run | |
| # takes 4-5s on ubuntu, 10-36s on macOS and 255s on windows-latest — ~94k files | |
| # materialised onto NTFS, not a slow download. Caching the posix legs would buy | |
| # seconds while evicting entries from a repo cache already at its 10 GB ceiling. | |
| # Same idiom and same shape as build.yml; the key must stay per-OS, caches do not | |
| # travel between runners. See decisions/2026/08/06/required-checks-wait-for-windows-packaging.md. | |
| - name: Restore node_modules cache (Windows only) | |
| id: cache-deps | |
| if: runner.os == 'Windows' | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ./node_modules | |
| key: bun-deps-windows-x64-${{ hashFiles('bun.lock') }} | |
| - name: Install dependencies | |
| # Skipped only on a Windows cache hit: on the posix legs the restore step above | |
| # never ran, so cache-hit is empty and this stays unconditional there. | |
| if: steps.cache-deps.outputs.cache-hit != 'true' | |
| run: bun install --frozen-lockfile | |
| - name: Save node_modules cache (Windows only) | |
| if: runner.os == 'Windows' && steps.cache-deps.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: ./node_modules | |
| key: bun-deps-windows-x64-${{ hashFiles('bun.lock') }} | |
| - name: Native shell launch pure tests | |
| run: bun run test:native-shell-launch | |
| # Seq 1540: the Codex config dev3 writes into ~/.codex/config.toml. A raw | |
| # Windows path in a TOML basic string makes `C:\Users` the escape `\U`, and | |
| # codex then refuses to start machine-wide. The tests decode the generated | |
| # file with a real TOML parser and drive ensureCodexConfigFile against a real | |
| # temp home, so only the Windows leg can see the composition that broke; the | |
| # POSIX legs are the control that the escaping did not change their output. | |
| # The status hooks are part of that same file now — they used to ride in as a | |
| # quoted `-c hooks={...}` argument and never survived the Windows command line | |
| # (Seq 1540) — so their block is decoded here by the same real parser. | |
| - name: Codex config generation + repair (real TOML parse) | |
| run: bun run test:codex-config-windows | |
| # Seq 1544: "+ Agent" on a native task handed the pane a hardcoded | |
| # `/bin/bash`, so adding a second agent died on Windows with "requested | |
| # shell executable not found". The spawn path is driven here against the | |
| # runner's REAL platform; the POSIX legs are the control that the launch | |
| # there is still `/bin/bash <script>.sh`. | |
| # `always()`: main was red at an unrelated Codex step on 2026-08-14 (run | |
| # 31839045471), and a step that only runs when an unrelated one passes is | |
| # evidence about that step, not about this one. | |
| - name: Agent spawn shell resolution (real platform) | |
| if: always() | |
| run: bun run test:agent-spawn-shell | |
| # Seq 1737: the agent command line was POSIX-quoted and then re-parsed by the | |
| # wrapper, so the apostrophe in "the task's title" reached PowerShell as `'\''` | |
| # and every Claude launch on Windows died with a ParserError before the binary | |
| # was looked up. Text is only half the question: PowerShell builds a raw command | |
| # line and the callee's C runtime splits it again, and 5.1 escapes nothing on the | |
| # way through. So the E2E RUNS the wrapper on this runner against a probe binary | |
| # and compares its argv byte-for-byte, including the real system prompt and a | |
| # binary whose path holds a space. The pure test pins both dialects and covers the | |
| # call site, which an E2E calling the helper cannot. | |
| # `always()`: a step that runs only when an unrelated one passes is evidence | |
| # about that step, not about this one. | |
| - name: Agent command line quoting (pure, both dialects) | |
| if: always() | |
| run: bunx vitest run --config vitest.config.bun.ts src/bun/__tests__/agent-launch-args.test.ts src/bun/__tests__/agent-command-golden.test.ts | |
| - name: Agent command line executed against a real binary (${{ matrix.os }}) | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:agent-launch-args-e2e | |
| # Seq 1547: the git-operation panes (rebase, push, merge) were hand-written | |
| # `#!/bin/bash` launched through `/bin/bash`, so they were dead on Windows. | |
| # These scripts rewrite history and touch the remote, so "the pane opened" | |
| # proves nothing — the E2E RUNS them against throwaway repos on this runner | |
| # and checks the operation completed: the remote ref moved, the rebase | |
| # replayed, the squash commit landed with its subject intact. It also drives | |
| # the conflict path, and it reads the verdict file back the way the app does | |
| # (PowerShell 5.1's `>` writes UTF-16LE with a BOM, which would make every | |
| # operation read as failed — invisible from macOS). | |
| # The pure test pins the POSIX text and asserts the Windows properties. | |
| # `always()` for the same reason as the step above: a step that runs only | |
| # when an unrelated one passes is evidence about that step. | |
| - name: Git-op pane scripts (pure, both dialects) | |
| if: always() | |
| run: bun run test:git-op-script | |
| - name: Git-op panes executed against real repos (${{ matrix.os }}) | |
| if: always() | |
| timeout-minutes: 6 | |
| run: bun run test:git-op-pane-e2e | |
| # Seq 1546: the Dev Server button was dead on Windows — it refused with "the | |
| # dev-server pane requires the tmux backend, which is POSIX-only" before it | |
| # read anything (issue #1387, reproduced by Arseny on his own box), and the | |
| # wrapper behind it was hand-written bash launched through `/bin/bash`. | |
| # Swapping only the launch would have been WORSE than the outage: PowerShell | |
| # would half-run bash text and look like it started. So the E2E RUNS the | |
| # wrapper on this runner and checks the dev command executed and read back | |
| # all three env blocks, and that a crashed dev server reports its code | |
| # instead of hanging on its own keypress prompt with no keyboard attached. | |
| # The pure test pins the POSIX text and asserts the Windows properties; the | |
| # launch test covers the call site, which an E2E calling the helper cannot. | |
| # `always()`: a step that runs only when an unrelated one passes is evidence | |
| # about that step, not about this one. | |
| - name: Dev-server pane script + launch (pure, both dialects) | |
| if: always() | |
| run: bun run test:dev-server-script | |
| - name: Dev-server pane wrapper executed (${{ matrix.os }}) | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:dev-server-pane-e2e | |
| - name: Windows self-update swap script (pure) | |
| run: bun run test:windows-update | |
| # The swap script is cmd.exe, tasklist and Get-Process — nothing about it can be | |
| # proved from macOS. This runs it for real against a live process: once where the | |
| # app exits on its own, once where it never does (the hang Arseny hit). | |
| - name: Windows self-update swap, executed against a real process | |
| if: matrix.os == 'windows-latest' | |
| run: bun run test:windows-update-e2e | |
| # THE PUBLISH PATH'S ONE WINDOWS-ONLY FAILURE MODE, RUN ON WINDOWS RATHER THAN | |
| # ASSERTED ABOUT IT. `create-release-artifacts.sh` runs under Git Bash on the win-x64 | |
| # build, whose `pwd` speaks MSYS (`/d/a/...`) while bun resolves only `D:\a\...`; the | |
| # first ever win-x64 job died on exactly that (run 31789301294). A macOS/Linux test | |
| # cannot see this class of bug — there the dialects are the same string — and one | |
| # already passed on the broken code. This job gates every publisher, so the proof | |
| # lands BEFORE a build reaches the bucket. Runs on all three legs: the POSIX runs are | |
| # the control that the suffix logic itself did not change. | |
| - name: Release publish-version path (real shell, real bun) | |
| run: bun run test:release-publish-version | |
| # Seq 1383: what each OS actually shows for an argv0-named host. The | |
| # per-platform assertions ARE the contract in decision 192, so they have to | |
| # run on all three runners — negative ones included (macOS Activity Monitor | |
| # and the Windows image-name column stay basename-only). | |
| # From here on every independent proof carries `if: always()`, for the reason the | |
| # pane-run step below already documents: these steps share nothing but the runner, | |
| # so the first failure used to hide up to fourteen later verdicts and one flake read | |
| # as a broad Windows outage. Build and upload steps deliberately keep the default — | |
| # a failed proof must still publish nothing. | |
| - name: Process naming visibility (${{ matrix.os }}) | |
| if: always() | |
| run: bun run test:process-naming | |
| # Real loopback proof for the Windows CLI control transport: round-trip, | |
| # two instances, stale record, token rejection, and the loopback-only | |
| # network boundary against this runner's real interfaces. | |
| - name: CLI loopback transport E2E | |
| if: always() | |
| run: bun run test:cli-loopback-e2e | |
| # The same transport driven through the COMPILED CLI (`dev3.exe` on this | |
| # runner): representative commands, instance selection, stale/corrupt | |
| # records, and the documented app-not-running exit code. | |
| - name: Packaged CLI over loopback E2E | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:cli-packaged-e2e | |
| # Seq 1548: `dev3 pane run` EXECUTED rather than asserted about. The pane-run | |
| # composition is authored per dialect and every existing test for it runs on | |
| # macOS, where PowerShell does not exist — so this runs the real runner against a | |
| # real spec file and reads the log back: non-ASCII output intact (the console code | |
| # page would mangle it), a non-zero exit code intact, and — Windows only — both | |
| # callers of the PowerShell lookup still launching with %SystemRoot% deleted from | |
| # the environment, where they used to throw. `always()` so a neighbour's failure | |
| # cannot hide this verdict. | |
| - name: Pane run executed (real shell, real log) | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:pane-run-exec | |
| # Merged host-image manifest: generator, validator, deterministic assembly, | |
| # additive staging outside the install root, and rollback selection. | |
| - name: Packaged host image + manifest tests | |
| if: always() | |
| run: bun run test:native-host-image | |
| # Seq 1550 investigation lever, dispatch-only because it costs minutes and gates | |
| # nothing: the lifecycle E2E's journal-isolation check fails on windows-latest | |
| # roughly one run in four, which no single run can settle. Two shells in ONE | |
| # process with no registry, no journal and no fan-out — arm A shares the user | |
| # profile as the E2E does, arm B gives each shell a private one — then the real | |
| # fixture repeated, so a one-in-four flake shows up inside a single dispatch. | |
| - name: Two-shell cross-session echo probe (dispatch only) | |
| if: always() && github.event_name == 'workflow_dispatch' | |
| env: | |
| DEV3_ECHO_PROBE_ROUNDS: "12" | |
| run: bun run test:native-cross-session-echo | |
| - name: Repeat the lifecycle E2E (dispatch only) | |
| if: always() && github.event_name == 'workflow_dispatch' | |
| shell: bash | |
| run: | | |
| fails=0 | |
| for i in $(seq 1 8); do | |
| echo "=== lifecycle repeat $i ===" | |
| if ! bun run test:native-registry-e2e; then fails=$((fails + 1)); fi | |
| done | |
| echo "lifecycle repeats failed: $fails / 8" | |
| # The fixture was changed to stop its own shell contaminating it, so the detector has | |
| # to be shown still armed: put one session's bytes into another's journal on purpose | |
| # and require the isolation check to name it, then restore both files. | |
| # | |
| # The mutation has to be a SHARED APPEND, not just a shared path: the writer replaces | |
| # its whole file atomically, so two sessions pointed at one path merely overwrite each | |
| # other and the last flush decides what a reader sees — a mutant that survived on | |
| # windows-latest while dying on macOS, which says nothing about the check. Appending | |
| # makes both sessions' frames present, which is the property under test. `perl -pi` | |
| # because BSD and GNU `sed -i` disagree on the backup argument, `\s*` between the | |
| # statements because a Windows checkout hands them over with CRLF endings, and the | |
| # applied-or-not guard because a mutation that silently did not apply reads exactly | |
| # like a mutant that survived. | |
| - name: Mutation check (dispatch only) — GREEN means the mutant applied AND the E2E caught it | |
| if: always() && github.event_name == 'workflow_dispatch' | |
| shell: bash | |
| run: | | |
| set -e | |
| echo "polarity: this step passes ONLY when the mutated E2E FAILS; red means the mutant survived or never applied" | |
| paths=src/bun/native-terminal-registry/paths.ts | |
| journal=src/bun/native-terminal-registry/journal.ts | |
| restore() { git checkout -- "$paths" "$journal"; } | |
| perl -pi -e 's{return join\(sessionDir\(id\), "journal\.ndjson"\);}{return join(sessionsRootDir(), "journal.ndjson");}' "$paths" | |
| perl -pi -e 's{import \{ renameSync, writeFileSync \} from "node:fs";}{import { appendFileSync, renameSync, writeFileSync } from "node:fs";}' "$journal" | |
| perl -0pi -e 's{const tmp = `\$\{this\.path\}\.\$\{process\.pid\}\.tmp`;\s*writeFileSync\(tmp, this\.frames\.join\(""\), \{ mode: 0o600 \}\);\s*renameSync\(tmp, this\.path\);}{appendFileSync(this.path, this.frames.join(""), { mode: 0o600 });}' "$journal" | |
| git diff --stat "$paths" "$journal" | |
| if ! git diff --quiet --exit-code -- "$paths" && ! git diff --quiet --exit-code -- "$journal"; then | |
| echo "mutation applied to both files" | |
| else | |
| restore | |
| echo "MUTATION NOT APPLIED — the patterns no longer match, so this check proves nothing" | |
| exit 1 | |
| fi | |
| if bun run test:native-registry-e2e; then | |
| restore | |
| echo "MUTATION SURVIVED — the isolation check no longer detects a foreign session's bytes" | |
| exit 1 | |
| fi | |
| restore | |
| echo "mutation killed: the isolation check caught the foreign session's bytes" | |
| # Real-runtime lifecycle regression for the persistent native-session | |
| # registry — runs on native Windows + POSIX with the pinned Bun 1.3.14. | |
| - name: Native-session registry lifecycle E2E | |
| if: always() | |
| run: bun run test:native-registry-e2e | |
| # Seq 1236 force-kills only the recorded host while journal/parser writes | |
| # are active, then proves owned-tree death and token-matched recovery. | |
| - name: Native-session host crash recovery E2E | |
| if: always() | |
| run: bun run test:native-crash-e2e | |
| # Seq 1237 writer/observer proof. The same real-runtime lifecycle runs on | |
| # native Windows and POSIX: concurrent attach, claim races, reconnect, and resize. | |
| - name: Native-session two-client lifecycle E2E | |
| if: always() | |
| run: bun run test:native-multi-client-e2e | |
| # Seq 1247 app-restart proof. Two separate short-lived controller processes: | |
| # controller A starts + marks + exits; controller B (a clean process) | |
| # rediscovers and reattaches to the same host/shell/session/pane + state, | |
| # with a deterministic single writer and honest lost-session results. | |
| - name: Native-session app-restart reattach E2E | |
| if: always() | |
| run: bun run test:native-app-restart-e2e | |
| # Seq 1381 cross-instance owner-routing proof for #1218. THREE separate | |
| # processes against one host: A holds the writer lease and serves the CLI's | |
| # NDJSON protocol, B binds the same pane as an observer and forwards one | |
| # delivery to A, C reopens and counts what reached the PTY. Windows exercises | |
| # the loopback `<pid>.endpoint.json` transport, POSIX the `<pid>.sock` one. | |
| - name: Native-pane cross-instance owner-routing E2E | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:native-owner-routing-e2e | |
| # Seq 1371 + 1381 PRODUCT proof of the same routing, entered from outside: | |
| # a `message.send` request arrives over the non-owning app's own CLI socket, | |
| # resolves a real task off disk, routes to the owning app and lands in native | |
| # pane-1 exactly once. POSIX only — the pane's shell is `stty -echo; exec cat`, | |
| # which has no PowerShell equivalent, so the Windows leg of owner routing is | |
| # covered by the E2E above instead. | |
| - name: Native message owner-routing E2E | |
| if: always() && runner.os != 'Windows' | |
| timeout-minutes: 6 | |
| run: bun run test:native-message-e2e | |
| # Seq 1292 PRODUCT proof: a task's primary terminal driven through | |
| # startNativeTaskTerminal/attach/stop — create, shell round-trip, resize, | |
| # detach, controller restart, observer refusal, owned-tree cleanup with a | |
| # live tmux sentinel, an honest null after cleanup, and the renderer | |
| # transport (pty-server WebSocket bridge with two real WS clients). | |
| - name: Product native task-terminal E2E | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:native-task-terminal-e2e | |
| # Seq 1254 single-view adapter parity. Drives the shared backend-neutral | |
| # parity corpus against the native adapter on native Windows + POSIX with | |
| # the pinned Bun 1.3.14 (single-view live + pure scenarios; multi-view | |
| # deferred to LAY-003/LAY-004). Expected final line: ALL CHECKS PASSED. | |
| # Completes in ~20s; the step timeout turns a future non-exiting run into a | |
| # fast, obvious failure instead of eating the job's whole 20-minute budget. | |
| - name: Native single-view adapter parity E2E | |
| if: always() | |
| timeout-minutes: 5 | |
| run: bun run test:native-parity-e2e | |
| # Seq 1283 native multi-pane coordinator. Runs the previously deferred | |
| # multi-view scenarios with REAL panes (2 then 6, PowerShell on Windows): | |
| # independent shells, non-crossing output, client-local focus/zoom, | |
| # writer-vs-observer resize, fresh-process reconnect to the same pids, | |
| # single-pane close, and full owned-tree cleanup. | |
| - name: Native multi-pane coordinator E2E | |
| if: always() | |
| timeout-minutes: 6 | |
| run: bun run test:native-multipane-e2e | |
| # Seq 1744: closing one pane must close that pane only. Driven through the | |
| # close RPC the renderer calls, not through the pane helpers beside it, so a | |
| # regression in the handler is caught too. The reported failure was on | |
| # Windows and the code is platform-neutral, which is exactly why this leg | |
| # runs it here as well as on POSIX. | |
| - name: Pane close keeps the task's terminal E2E | |
| if: always() | |
| timeout-minutes: 6 | |
| run: bun run test:aux-pane-close-e2e | |
| - name: Explicit Windows shell launch matrix | |
| if: always() && runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| & .\src\bun\native-terminal-registry\__tests__\run-windows-shell-matrix.ps1 ` | |
| -OutDir "${{ runner.temp }}\dev3-windows-shell-matrix" | |
| - name: Upload Windows shell launch evidence | |
| if: always() && runner.os == 'Windows' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: windows-shell-launch-matrix | |
| path: ${{ runner.temp }}\dev3-windows-shell-matrix\ | |
| # Seq 1228 live-parser proof. Gates ONLY the deferred path; `callback` | |
| # mode is printed as evidence and is EXPECTED to fail on Windows | |
| # Bun 1.3.14 (the preserved seq 1185 reproduction, decision 146). | |
| - name: Live-parser regression probe (callback vs deferred) | |
| if: always() | |
| run: bun src/bun/native-terminal-registry/regression-probe.ts both | |
| - name: Live-parser lifecycle E2E | |
| if: always() | |
| run: bun run test:native-live-parser-e2e | |
| - name: Bundle terminal host entrypoint | |
| if: runner.os == 'Windows' | |
| run: bun run build:native | |
| - name: Build packaged runtime tracer | |
| working-directory: scripts/fixtures/windows-conpty-package | |
| run: bun ../../../node_modules/electrobun/bin/electrobun.cjs build --env=canary | |
| - name: Upload package and proof | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: windows-conpty-package | |
| path: | | |
| scripts/fixtures/windows-conpty-package/artifacts/ | |
| # The REAL dev3 Windows package, not the isolated tracer fixture: proves the | |
| # versioned native host image ships inside the final update archive and that | |
| # it stages, launches detached, reattaches, and stops from there with no Bun | |
| # on PATH. Separate job so its ~10 minutes do not eat the matrix job's budget. | |
| windows-app-archive: | |
| runs-on: windows-latest | |
| timeout-minutes: 35 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup build Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.14" | |
| # Same 255s-on-Windows install as the matrix job above, paid a second time in | |
| # parallel, so both jobs have to be cached or the gate's duration does not move. | |
| # Same key on purpose: same lockfile, same runner, one entry serves both. | |
| - name: Restore node_modules cache | |
| id: cache-deps | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ./node_modules | |
| key: bun-deps-windows-x64-${{ hashFiles('bun.lock') }} | |
| - name: Install dependencies | |
| if: steps.cache-deps.outputs.cache-hit != 'true' | |
| run: bun install --frozen-lockfile | |
| - name: Save node_modules cache | |
| if: steps.cache-deps.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: ./node_modules | |
| key: bun-deps-windows-x64-${{ hashFiles('bun.lock') }} | |
| # postBuild assembles + validates the host image inside the bundle; | |
| # postPackage re-verifies it from the final `.tar.zst` and drives the | |
| # detached lifecycle from the staged copy. | |
| # | |
| # `package:win-archive` passes electrobun `--env=canary`, and the build below is | |
| # handed to a human as-is. That is safe under a CONDITION, not as a property: today | |
| # the channel is a NAME only — electrobun's naming.ts suffixes artifact and app | |
| # names and records the channel in build.json, nothing else, and no Windows | |
| # update.json is published for any channel, so an unsigned CI build cannot behave | |
| # differently from a stable one. Seq 1443 is landing real channel semantics; when | |
| # it does, re-examine whether a human should get a canary-channel build here. | |
| - name: Build the real dev3 Windows package and verify the final archive | |
| run: bun run package:win-archive | |
| # Whole-app proof on top of the native-host lifecycle one: the archive ships | |
| # the desktop executable + bundled dev3.exe + manifest-validated host image, | |
| # and the extracted executable reaches the ready marker and dies completely. | |
| - name: Launch the packaged Windows app and prove clean shutdown | |
| timeout-minutes: 10 | |
| env: | |
| DEV3_REQUIRE_WINDOWS_PROOF: "1" | |
| # Extract somewhere durable instead of a temp workspace that dies with the | |
| # script, so the tree uploaded below is the one this proof actually spawned — | |
| # not a look-alike re-extracted from the same archive afterwards. | |
| DEV3_WINDOWS_APP_UNPACK_DIR: ${{ github.workspace }}\windows-app-unpacked | |
| run: bun run verify:win-app-launch | |
| - name: Upload real Windows archive proof | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: windows-app-archive-proof | |
| path: | | |
| artifacts/windows-conpty-package-proof.json | |
| artifacts/windows-app-layout.json | |
| artifacts/windows-app-launch-proof.json | |
| artifacts/*-update.json | |
| # The downloadable build, deliberately NOT folded into the proof artifact above: | |
| # that one is JSON for CI and gets opened constantly, this one is the whole app for a | |
| # human and gets opened rarely. Merging them would make every proof download drag the | |
| # payload behind it. | |
| # | |
| # SIZES, MEASURED, because ~400 MB was quoted here for a long time and is only true of | |
| # the tree on disk: GitHub compresses an artifact, so this one downloads at ~124 MB | |
| # (windows-app-e4b5fcbf5…, 130 055 428 bytes) and the release zip built from the same | |
| # tree is 121.0 MB (run 31799430704, on windows-latest). | |
| # | |
| # The EXTRACTED tree, not the `.tar.zst`: Windows tar.exe cannot read zstd (this | |
| # job ships electrobun's zig-zstd.exe precisely because of that), so the archive is | |
| # unopenable without extra tooling, while GitHub zips an artifact on download and | |
| # Explorer extracts it with nothing installed. The self-extracting Setup .exe the | |
| # build also produces is NOT uploaded: nothing has ever launched it, and the file a | |
| # summary tells a human to open must be a file the proof launched. That installer | |
| # is a KNOWN GAP, not a rejected idea — see | |
| # decisions/2026/08/06/downloadable-windows-build-is-the-launched-tree.md before assuming | |
| # it was vetted. | |
| # | |
| # NO `if: always()` here, on purpose: a failed launch proof must publish nothing. | |
| # `always()` would look like a debugging convenience and would instead hand out | |
| # builds that never reached a window, wearing a green download banner. | |
| - name: Upload the launched Windows app for download | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: windows-app-${{ github.sha }} | |
| path: windows-app-unpacked/ | |
| # 30 days, not the 90-day default. Storage is free on a public repo, so this is | |
| # not a cost call: a months-old build still listed is one somebody downloads | |
| # believing it is current. An expired build is re-run, not lost. | |
| retention-days: 30 | |
| # An empty upload would publish a green "download it here" summary pointing at | |
| # nothing at all. | |
| if-no-files-found: error | |
| # Reads the launch proof and renders the entry point out of it, so this text cannot | |
| # name an executable the proof did not start. Same reason it has no `if:` as above. | |
| - name: Explain how to run the downloadable Windows build | |
| env: | |
| DEV3_WINDOWS_ARTIFACT_NAME: windows-app-${{ github.sha }} | |
| DEV3_WINDOWS_ARTIFACT_RETENTION_DAYS: "30" | |
| run: bun scripts/windows-download-summary.ts | |
| # The REAL dev3 macOS and Linux packages. Synthesized bundle trees in vitest | |
| # cannot prove a package ships a launchable host, so this builds the actual | |
| # package, lets postBuild assemble + stage + drive the detached host with no | |
| # Bun on PATH, and then proves a bundled resolver (no source checkout, no env | |
| # override) reaches that image from the packaged runtime. | |
| posix-app-package: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup build Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.14" | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| # postBuild → scripts/package-posix-native-host.ts: assemble the versioned | |
| # image into the bundle, stage it outside the install root, then version / | |
| # start / reattach / stop the detached host from the staged copy. | |
| - name: Build the real dev3 package | |
| run: bun run build | |
| - name: Resolve the packaged host image from the packaged runtime | |
| run: bun scripts/verify-packaged-host-resolution.ts | |
| - name: Upload package proof | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: posix-native-host-package-proof-${{ matrix.os }} | |
| path: | | |
| build/*/native-host-package-proof.json | |
| build/*/native-host-resolution-proof.json |