Skip to content

Commit 955b4bb

Browse files
committed
Package a versioned native terminal host image on Windows
Assemble the packaged Bun carrier, bundled host entrypoint, and a merged manifest into a content-tagged image inside the real dev3 Windows bundle, so it ships in the final update archive, and verify that archive end to end: validate sizes/checksums/runtime/protocol/OS-arch/entrypoint/archive paths, stage the image additively outside the installation root, drive detached start, reattach, and stop with no Bun on PATH, then stage a second image beside the live one and assert the older image is byte-identical and still selectable for rollback. Merge the artifact manifest into the host-images module so it is type-checked and covered by vitest, and gate it in CI together with a new job that builds the real Windows package.
1 parent 1682dc9 commit 955b4bb

24 files changed

Lines changed: 1922 additions & 328 deletions

.github/workflows/windows-conpty-package.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- "scripts/build-windows-terminal-host.ts"
1212
- "scripts/verify-packaged-windows-conpty.ts"
1313
- "scripts/verify-windows-conpty-update-archive.ts"
14+
- "scripts/native-terminal-host-manifest/**"
1415
- "src/bun/native-terminal-host/**"
1516
- "src/shared/native-terminal-runtime.ts"
1617
- "src/bun/prototypes/detached-pty/**"
@@ -43,6 +44,11 @@ jobs:
4344
- name: Native shell launch pure tests
4445
run: bun run test:native-shell-launch
4546

47+
# Merged host-image manifest: generator, validator, deterministic assembly,
48+
# additive staging outside the install root, and rollback selection.
49+
- name: Packaged host image + manifest tests
50+
run: bun run test:native-host-image
51+
4652
# Real-runtime lifecycle regression for the persistent native-session
4753
# registry — runs on native Windows + POSIX with the pinned Bun 1.3.14.
4854
- name: Native-session registry lifecycle E2E
@@ -113,3 +119,36 @@ jobs:
113119
name: windows-conpty-package
114120
path: |
115121
scripts/fixtures/windows-conpty-package/artifacts/
122+
123+
# The REAL dev3 Windows package, not the isolated tracer fixture: proves the
124+
# versioned native host image ships inside the final update archive and that
125+
# it stages, launches detached, reattaches, and stops from there with no Bun
126+
# on PATH. Separate job so its ~10 minutes do not eat the matrix job's budget.
127+
windows-app-archive:
128+
runs-on: windows-latest
129+
timeout-minutes: 35
130+
steps:
131+
- uses: actions/checkout@v5
132+
133+
- name: Setup build Bun
134+
uses: oven-sh/setup-bun@v2
135+
with:
136+
bun-version: "1.3.14"
137+
138+
- name: Install dependencies
139+
run: bun install --frozen-lockfile
140+
141+
# postBuild assembles + validates the host image inside the bundle;
142+
# postPackage re-verifies it from the final `.tar.zst` and drives the
143+
# detached lifecycle from the staged copy.
144+
- name: Build the real dev3 Windows package and verify the final archive
145+
run: bun run package:win-archive
146+
147+
- name: Upload real Windows archive proof
148+
if: always()
149+
uses: actions/upload-artifact@v5
150+
with:
151+
name: windows-app-archive-proof
152+
path: |
153+
artifacts/windows-conpty-package-proof.json
154+
artifacts/*-update.json
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The Windows build now assembles a versioned native terminal host image — the packaged Bun runtime carrier, the bundled host entrypoint, and a merged manifest with per-file checksums — into the real dev3 package, so it ships inside the final update archive and is verified from that exact archive. Staging the image is additive and lands outside the replaceable installation directory, so an older image keeps serving a running host while a newer one is staged beside it and rollback can select either. tmux remains the default terminal backend and nothing here starts the native host.

decisions/164-native-terminal-host-manifest-generator.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,21 @@ NOT run under `bun run test`/`bun run lint` or in CI (the task also forbids addi
3636
a CI check). Type-check the module directly with
3737
`bunx tsc --ignoreConfig --noEmit --strict ... manifest.ts generate.ts`.
3838

39+
## Superseded by 169
40+
41+
Once packaging started consuming the manifest, the module moved into
42+
`src/bun/native-terminal-registry/host-images/artifact-manifest.ts` (CLI logic in
43+
`artifact-manifest-cli.ts`, a thin shim left at
44+
`scripts/native-terminal-host-manifest/generate.ts`). The core contract above is
45+
unchanged; the runner, the type-check path, and the CI gap are not — see
46+
`169-packaged-windows-native-host-image.md`. Tests now run under vitest via
47+
`bun run test:native-host-image` and are gated in CI.
48+
3949
## Risks
4050

41-
Tests are not wired into CI, so a regression here is caught only by running the
42-
Bun test command above. Acceptable while the module is production-unused; when a
43-
packaging step starts consuming it, wire the tests into a checked path then.
51+
(Historic, resolved by 169.) Tests were not wired into CI, so a regression here
52+
was caught only by running the Bun test command above. Acceptable while the module
53+
was production-unused.
4454

4555
## Alternatives considered
4656

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# 169 — Package the versioned native host image inside the Windows archive
2+
3+
## Context
4+
5+
RUN-006 / WIN-004 (parent seq 1141, tmux removal) needed the packaged-runtime
6+
proof and the deterministic artifact manifest to become a real, versioned host
7+
image that ships inside the production Windows update archive — installable on
8+
demand, outside the replaceable application directory, without enabling the
9+
native backend. tmux stays the production default and its packaged artifacts are
10+
untouched.
11+
12+
Two manifests already existed and described the same artifact from different
13+
angles: `image-manifest.ts` (immutable identity: tag, protocol version,
14+
entrypoint, runtime floor) and the `scripts/` artifact manifest (deterministic
15+
per-file size + SHA-256, Bun version, OS/arch). Neither knew where the image sits
16+
inside an archive, and the artifact manifest lived outside `tsconfig.json`'s
17+
`include` and outside every vitest root, so it was neither type-checked nor run
18+
in CI.
19+
20+
## Investigation
21+
22+
Electrobun's hook order settles where the image can be built
23+
(`node_modules/electrobun/src/cli/index.ts`): `postBuild` runs at line 3435,
24+
`createTar` at 3761, `postPackage` at 4243. The packaged Bun runtime only exists
25+
inside the build directory, and only content written under the app bundle before
26+
`createTar` reaches the archive. So the image must be assembled in `postBuild`,
27+
into the bundle directory that becomes the archive's top-level entry — which is
28+
also why `archiveRoot` is recorded relative to that bundle root rather than to
29+
the tarball root.
30+
31+
## Decision
32+
33+
- **Merged manifest**`host-images/packaged-image-manifest.ts` embeds the
34+
artifact manifest under `artifact` and adds `tag`, `runtimeFloor`,
35+
`runtimeCarrier`, and `archiveRoot`. `validatePackagedHostImageManifest`
36+
re-verifies sizes, SHA-256s, entrypoint, carrier, runtime floor, tag shape,
37+
archive path, and caller expectations (OS/arch/Bun/protocol/tag) as typed
38+
`ManifestError` codes. No clock is read, so the manifest is byte-stable.
39+
- **Content-derived tag**`<bunVersion>-p<protocolVersion>-<digest12>`. Two
40+
builds of the same bytes agree; a changed entrypoint lands beside the old image
41+
instead of replacing it.
42+
- **Assembly at `postBuild`, verification at `postPackage`** — the real
43+
`electrobun.config.ts` gained `postPackage`; `scripts/verify-packaged-windows-conpty.ts`
44+
assembles + validates on the way in and, in archive mode, discovers the shipped
45+
image, stages it, drives detached start / reattach / stop with no Bun on PATH,
46+
then stages a second image beside the live one and asserts the old one is
47+
byte-identical and still selectable for rollback.
48+
- **Staging root outside the install directory**
49+
`hostImagesRootDir()` = additive `~/.dev3.0/native-host-images/`
50+
(`DEV3_NATIVE_HOST_IMAGES_DIR` override). `stagePackagedImage` copies into a
51+
dot-prefixed scratch directory (invisible to every reader), validates, then
52+
moves it into place; an existing tag is never overwritten and a corrupt copy is
53+
discarded and reported.
54+
- **Manifest module moved into `src/`**`scripts/native-terminal-host-manifest/manifest.ts`
55+
became `host-images/artifact-manifest.ts`, its CLI logic
56+
`artifact-manifest-cli.ts` (the `scripts/` file is now a thin shim). It is now
57+
type-checked by `bun run lint`, runs under `bun run test`, and is gated in CI
58+
via `bun run test:native-host-image`.
59+
60+
## Risks
61+
62+
- The new `windows-app-archive` CI job builds the real Windows package, which is
63+
a heavier and less-exercised path than the tracer fixture; a failure there can
64+
be an Electrobun packaging problem rather than a host-image problem. The proof
65+
JSON names which stage failed.
66+
- Assembly needs `ELECTROBUN_APP_VERSION`, so the script only works as an
67+
Electrobun hook, not standalone.
68+
- Windows code signing and uninstall are still **not** covered: Electrobun has no
69+
Windows signing path and this change invented no credentials, so the RUN-006
70+
signing/uninstall gap remains open. The image is unsigned, exactly like the rest
71+
of the Windows package.
72+
73+
## Alternatives considered
74+
75+
- **Assemble into `dist/native/` and rely on a copy rule** — rejected: the
76+
packaged Bun runtime does not exist at that point, so dev3 would have to
77+
download and pin a second Bun itself.
78+
- **Assemble on first launch from the installed package** — rejected: then the
79+
image is not *in* the archive, so nothing can be verified at build time.
80+
- **Keep the two manifests separate and validate twice** — rejected: the archive
81+
path has to be checked against the same file table it describes, and two
82+
independent schemas would drift.
83+
- **A fourth vitest project rooted at `scripts/`** — rejected: moving the module
84+
into `src/` gets type-checking and CI for free (this supersedes the
85+
"not wired into CI" risk recorded in decision 164).

electrobun.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export default {
6161
},
6262
},
6363
scripts: {
64+
// Windows only (both no-op elsewhere): postBuild assembles the versioned
65+
// native host image into the bundle so it ships inside the update archive,
66+
// postPackage re-verifies that image from the FINAL archive.
6467
postBuild: "./scripts/verify-packaged-windows-conpty.ts",
68+
postPackage: "./scripts/verify-windows-conpty-update-archive.ts",
6569
},
6670
} satisfies ElectrobunConfig;

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"test:native-shell-launch": "bunx vitest run --config vitest.config.bun.ts src/bun/native-terminal-registry/__tests__/shell-launch.test.ts src/bun/native-terminal-registry/__tests__/shell-probe.test.ts src/bun/native-terminal-registry/__tests__/windows-shell-runner.test.ts src/bun/native-terminal-registry/__tests__/windows-shell-evidence.test.ts src/bun/native-terminal-registry/__tests__/windows-shell-matrix-support.test.ts src/bun/native-terminal-registry/__tests__/host-config.test.ts src/bun/native-terminal-registry/__tests__/protocol.test.ts src/bun/native-terminal-registry/__tests__/registry.test.ts src/bun/native-terminal-registry/__tests__/client.test.ts src/bun/native-terminal-registry/__tests__/isolation.test.ts",
2828
"test:native-live-parser-e2e": "bun src/bun/native-terminal-registry/__tests__/live-parser.bun-e2e.ts",
2929
"test:native-host-images-e2e": "bun src/bun/native-terminal-registry/host-images/__tests__/lifecycle.bun-e2e.ts",
30+
"test:native-host-image": "bunx vitest run --config vitest.config.bun.ts src/bun/native-terminal-registry/host-images/__tests__/artifact-manifest.test.ts src/bun/native-terminal-registry/host-images/__tests__/artifact-manifest-cli.test.ts src/bun/native-terminal-registry/host-images/__tests__/packaged-image-manifest.test.ts src/bun/native-terminal-registry/host-images/__tests__/packaged-image.test.ts",
31+
"package:win-archive": "bun scripts/generate-build-info.ts && bun scripts/generate-changelog.ts && vite build && bun run build:cli && electrobun build --env=canary",
3032
"test:native-parity-e2e": "bun src/bun/native-terminal-adapter/__tests__/parity-corpus.native-e2e.ts",
3133
"test:terminal-state-spike": "bunx vitest run --config vitest.config.bun.ts src/bun/prototypes/terminal-state/__tests__",
3234
"benchmark:terminal-state-spike": "bun src/bun/prototypes/terminal-state/benchmark.ts",

scripts/fixtures/windows-conpty-package/electrobun.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default {
2525
generatePatch: false,
2626
},
2727
scripts: {
28+
postBuild: "../../verify-packaged-windows-conpty.ts",
2829
postPackage: "../../verify-windows-conpty-update-archive.ts",
2930
},
3031
} satisfies ElectrobunConfig;

scripts/native-terminal-host-manifest/README.md

Lines changed: 0 additions & 72 deletions
This file was deleted.

scripts/native-terminal-host-manifest/__tests__/cli.test.ts

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)