Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
0c7acc0
feat(lifecycle): add standalone Rust process supervisor
roodboi Nov 14, 2026
2208173
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
6a57cdb
fix(lifecycle): pass macOS supervisor lint without redundant cast
roodboi Nov 14, 2026
1339eb5
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
12e8976
feat(lifecycle): opt into native supervisor for host processes
roodboi Nov 14, 2026
9f60e7d
fix(lifecycle): isolate unreadable project state in global observations
roodboi Nov 14, 2026
bb285fd
fix(lifecycle): exclude unreadable-state placeholders from container …
roodboi Nov 14, 2026
4b3d1ae
merge: preserve supervisor and interactive session model registries
roodboi Nov 14, 2026
f8a9b67
merge: retain standalone supervisor alongside interactive session models
roodboi Nov 14, 2026
55aec84
fix(lifecycle): recover receipts across case-only command renames
roodboi Nov 14, 2026
b5b9860
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
c2bc1ff
fix(lifecycle): isolate supervisor control and serialize doctor retir…
roodboi Nov 14, 2026
597eb47
fix(lifecycle): accept inherited subprocess environment names
roodboi Nov 14, 2026
bd9d8f4
feat(lifecycle): package supervisor with CLI distributions
codex Nov 14, 2026
83f4f34
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
ff1e481
fix(lifecycle): place protocol regressions after implementation
roodboi Nov 14, 2026
dc1408e
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
e6b38ad
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
7bc415f
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
3f6dcad
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
a6e9b98
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
a5481c7
test(lifecycle): budget native controller cases for macOS
roodboi Nov 14, 2026
b8d89d0
fix(lifecycle): preflight supervisor and retain uncertain ownership
roodboi Nov 14, 2026
9326619
test(lifecycle): cover immediate supervisor definition replacement
roodboi Nov 14, 2026
766a3af
fix(lifecycle): recover native orphan sessions and bound idle work
roodboi Nov 14, 2026
01a4e24
fix(lifecycle): merge verified native recovery and launch leases
roodboi Nov 14, 2026
30b3453
fix(lifecycle): finish retirement before adopting commands
roodboi Nov 14, 2026
1a8d027
docs(lifecycle): describe verified orphan recovery and idle bounds
roodboi Nov 14, 2026
80550f2
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
6b9a982
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
30cd31d
fix(lifecycle): fence retired generations and survive helper replacement
roodboi Nov 14, 2026
e8511bd
fix(lifecycle): cancel delayed launches after generation retirement
roodboi Nov 14, 2026
42b7f82
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
0ff8c9a
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
047175a
test(lifecycle): observe unreaped exit without Darwin session identity
roodboi Nov 14, 2026
f353c3d
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
08ac8b8
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
e2130cf
fix(lifecycle): verify Linux process UID independently of proc ownership
roodboi Nov 14, 2026
83a7925
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
a0d2c02
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
e203585
test(lifecycle): confirm fixture zombies with non-reaping wait
roodboi Nov 14, 2026
fe144ec
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
8304058
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
1bccead
test(lifecycle): preserve frozen maintenance across supervisor builds
roodboi Nov 14, 2026
768c3f0
test(lifecycle): canonicalize temporary homes before isolation checks
roodboi Nov 14, 2026
10539a0
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
f82bb58
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
7d8304d
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
bd47a1a
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
d60d4e2
fix(supervisor): fence orphan recovery against session ID reuse
roodboi Nov 14, 2026
f13b2c8
test(cli): disable daemon autostart in env plan acceptance
roodboi Nov 14, 2026
0301ffc
test(cli): identify unexpected env plan fixture state
roodboi Nov 14, 2026
f9ae813
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
4127a94
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
7a86e39
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 14, 2026
23548e0
test(supervisor): qualify both guard launch crash windows
roodboi Nov 14, 2026
d6e7ba1
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
e2e8a30
test(supervisor): await quiet command before guard crash
roodboi Nov 14, 2026
3691c42
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 14, 2026
b228781
fix(supervisor): retain recovery authority after witness exit
roodboi Nov 14, 2026
25772b6
fix(packaging): refresh reviewed toolchain recipe pins
roodboi Nov 14, 2026
30dde38
test(supervisor): reap paused caller on failed zombie assertion
roodboi Nov 14, 2026
0900133
test(supervisor): report receipt on failed owned stop
roodboi Nov 14, 2026
71fa093
fix(supervisor): retain zombie guard birth proof on Darwin
roodboi Nov 14, 2026
7dca617
fix(supervisor): inspect unreaped Darwin guard records
roodboi Nov 14, 2026
f952a17
fix(supervisor): retain stop admission recovery authority
roodboi Nov 14, 2026
bdc9afc
fix(supervisor): preserve recovered stop grace and guard admission
roodboi Nov 14, 2026
2288c40
fix(supervisor): recover guard loss during stop census
roodboi Nov 14, 2026
d508e2e
merge: propagate reviewed supervisor recovery fixes
roodboi Nov 14, 2026
06c486d
merge: propagate reviewed supervisor recovery into packaging
roodboi Nov 14, 2026
59bd8d5
test(supervisor): make guard-loss recovery regressions deterministic
roodboi Nov 14, 2026
98d0b0a
test(supervisor): wait for kernel-observed guard stop before guard loss
roodboi Nov 14, 2026
2b3b3b2
merge: propagate deterministic guard-loss regression from #802
roodboi Nov 14, 2026
81937be
merge: propagate deterministic guard-loss regression from #802 via #811
roodboi Nov 14, 2026
1c9c9b3
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 14, 2026
207f3f8
merge: propagate #811 (next and #802 test updates) into packaging
roodboi Nov 14, 2026
616b2f5
fix(lifecycle): refuse lost ownership state after supervisor use
roodboi Nov 14, 2026
628419c
merge: propagate #811 lost-state refusal into packaging
roodboi Nov 14, 2026
b2f997f
fix(release): refuse upgrades whose supervisor companion mismatches i…
roodboi Nov 14, 2026
e5bdf57
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 15, 2026
4b17889
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
d7eae0c
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
2fb3e0e
test(e2e): register the recovery scenario's tmux root with the socket…
roodboi Nov 15, 2026
0953db6
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
2d9c4ce
fix(supervisor): treat pidfd_open EINVAL for an exited group leader a…
roodboi Nov 15, 2026
bf664de
test(supervisor): pin pidfd_open EINVAL handling behind a seam
roodboi Nov 15, 2026
e6678df
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
f9c6692
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
1fbf333
test(supervisor): kill and reap pidfd seam members on any failure
roodboi Nov 15, 2026
682b260
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
d0b0d6a
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
033358a
fix(supervisor): frame every stop and status reply and count threaded…
roodboi Nov 15, 2026
b4317ec
fix(supervisor): never report success when the reply's receipt read f…
roodboi Nov 15, 2026
f4426a3
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
efe1582
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
3fc81d0
fix(lifecycle): let incremental add wait for the server's whole stop-…
roodboi Nov 15, 2026
cf93367
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
63614d6
test(lifecycle): retry ETXTBSY when spawning a just-copied supervisor
roodboi Nov 15, 2026
a6be724
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
60d737b
fix(supervisor): reread a receipt replaced while it was being opened
roodboi Nov 15, 2026
f90a049
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
9ce8214
test(supervisor): give the receipt replacement test the display_name …
roodboi Nov 15, 2026
8b6d3ba
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
88f8546
test(e2e): register the host-process scenario's tmux root with the so…
roodboi Nov 15, 2026
9883fa0
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
eb47cc8
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 15, 2026
8ffd3c0
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
326268a
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
ca0fa30
fix(supervisor): append log records without a lock so no guard waits …
roodboi Nov 15, 2026
15bb1d0
docs(supervisor): separate stop and launch lock-wait outcomes; name s…
roodboi Nov 15, 2026
b2b8d53
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 15, 2026
80da56e
Merge branch 'feat/lifecycle-supervisor' into feat/lifecycle-supervis…
roodboi Nov 15, 2026
a9bc571
docs(lifecycle): list serve, add and status lock waits in the supervi…
roodboi Nov 15, 2026
d9250a1
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
897cb3e
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 15, 2026
016946d
Merge branch 'feat/lifecycle-supervisor-opt-in' into feat/lifecycle-s…
roodboi Nov 15, 2026
f15b3f5
Merge remote-tracking branch 'origin/next' into feat/lifecycle-superv…
roodboi Nov 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,13 @@ jobs:
"$RUNNER_TEMP/config-schema/bin/python" scripts/check-config-schema.py
- name: Test transport, projections and compiled bundle without host toolchains
run: |
bunx ultracite check scripts/build-config-compiler.ts scripts/build-release.ts scripts/check-release-config-compiler.ts scripts/check-config-compiler-cli.ts tests/config-compiler-build.test.ts tests/native-config-compiler.test.ts tests/native-config-command.test.ts tests/native-config-dto.test.ts tests/native-project-inputs.test.ts tests/native-project-inputs-acquisition-race.test.ts tests/release-download-installer.test.ts
bunx ultracite check scripts/build-lifecycle-supervisor.ts scripts/check-release-lifecycle-supervisor.ts scripts/build-config-compiler.ts scripts/build-release.ts scripts/check-release-config-compiler.ts scripts/check-config-compiler-cli.ts tests/config-compiler-build.test.ts tests/native-config-compiler.test.ts tests/native-config-command.test.ts tests/native-config-dto.test.ts tests/native-project-inputs.test.ts tests/native-project-inputs-acquisition-race.test.ts tests/release-download-installer.test.ts
bun test tests/config-compiler-build.test.ts tests/native-config-compiler.test.ts tests/native-config-command.test.ts tests/native-config-dto.test.ts tests/native-project-inputs.test.ts tests/native-project-inputs-acquisition-race.test.ts
bun run --cwd packages/cli typecheck
bun test tests/release-download-installer.test.ts
bun run build:release --skip-tests --out=dist/config-compiler-release
bun scripts/check-release-config-compiler.ts dist/config-compiler-release
bun scripts/check-release-lifecycle-supervisor.ts dist/config-compiler-release
bun scripts/check-config-compiler-cli.ts
bunx ultracite check scripts/check-native-env-plan-cli.ts tests/native-env-plan-transport.test.ts tests/native-env-metadata.test.ts
bun test tests/native-env-plan-transport.test.ts tests/native-env-metadata.test.ts tests/project-env-config.test.ts tests/project-env-metadata.test.ts
Expand Down Expand Up @@ -650,7 +651,7 @@ jobs:
--tag hack-runtime-ci:slim
- name: Smoke slim runtime image defaults
run: |
docker run --rm --entrypoint sh hack-runtime-ci:slim -lc 'command -v bun >/dev/null && command -v hack >/dev/null && test "${HACK_EXECUTION_MODE}" = "codex" && test "${HACK_DAEMON_DISABLE_DOCKER_EVENTS}" = "1" && hack --help >/tmp/hack-help.txt && grep -q "Usage:" /tmp/hack-help.txt'
docker run --rm --entrypoint sh hack-runtime-ci:slim -lc 'command -v bun >/dev/null && command -v hack >/dev/null && test -x /usr/local/bin/hack-lifecycle-supervisor && test "${HACK_EXECUTION_MODE}" = "codex" && test "${HACK_DAEMON_DISABLE_DOCKER_EVENTS}" = "1" && hack --help >/tmp/hack-help.txt && grep -q "Usage:" /tmp/hack-help.txt'
- name: Smoke slim runtime mounted-project env flow
run: bash scripts/portable-container-smoke.sh hack-runtime-ci:slim linux/amd64

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ jobs:
run: bun run build:release "--version=$RELEASE_VERSION" --skip-tests
- name: Qualify installed release compiler without host runtimes or network
run: bun scripts/check-release-config-compiler.ts dist/release
- name: Qualify installed lifecycle supervisor
run: bun scripts/check-release-lifecycle-supervisor.ts dist/release
- name: Upload tarball
env:
GH_TOKEN: ${{ github.token }}
Expand Down
1 change: 1 addition & 0 deletions .hack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ hack run --profile toolchain toolchain -- test
hack run --profile toolchain toolchain -- check
hack run --profile toolchain toolchain -- rust
hack run --profile toolchain toolchain -- rust-check
hack run --profile toolchain toolchain -- lifecycle-supervisor
hack run --profile toolchain toolchain -- config-compiler
hack run --profile toolchain toolchain -- build
hack run --profile toolchain toolchain -- exec bun index.ts --help
Expand Down
6 changes: 3 additions & 3 deletions .hack/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"inputs": [
{
"path": ".hack/toolchain/run.sh",
"sha256": "0690919e0937c49638ea1c8318d731df3c45df26c3865c6a37ae263e8854e6a5"
"sha256": "d71945600ee66cf53a2ffb23f7aa7603fd217fde8a6875e97668b72ccce4fad5"
},
{
"path": ".hack/toolchain/Dockerfile",
Expand Down Expand Up @@ -97,7 +97,7 @@
"inputs": [
{
"path": ".hack/toolchain/run.sh",
"sha256": "0690919e0937c49638ea1c8318d731df3c45df26c3865c6a37ae263e8854e6a5"
"sha256": "d71945600ee66cf53a2ffb23f7aa7603fd217fde8a6875e97668b72ccce4fad5"
},
{
"path": ".hack/toolchain/Dockerfile",
Expand Down Expand Up @@ -154,7 +154,7 @@
"inputs": [
{
"path": ".hack/toolchain/run.sh",
"sha256": "0690919e0937c49638ea1c8318d731df3c45df26c3865c6a37ae263e8854e6a5"
"sha256": "d71945600ee66cf53a2ffb23f7aa7603fd217fde8a6875e97668b72ccce4fad5"
},
{
"path": ".hack/toolchain/Dockerfile",
Expand Down
4 changes: 3 additions & 1 deletion .hack/toolchain/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ case "$task" in
check) install_deps; bun run typecheck; exec bun run check ;;
rust-check) cargo fmt --manifest-path packages/runtime-core/Cargo.toml --check; exec cargo clippy --locked --manifest-path packages/runtime-core/Cargo.toml --target-dir /build/rust --all-targets --all-features --jobs 2 -- -D warnings ;;
rust) exec cargo test --all-features --locked --manifest-path packages/runtime-core/Cargo.toml --target-dir /build/rust --jobs 2 "$@" ;;
lifecycle-supervisor) exec bun scripts/build-lifecycle-supervisor.ts ;;
config-compiler)
install_deps
cargo fmt --manifest-path packages/config-compiler/Cargo.toml --check
Expand All @@ -30,6 +31,7 @@ case "$task" in
;;
build)
install_deps
bun scripts/build-lifecycle-supervisor.ts
# Bun 1.3.9 stages in cwd; its cross-device fallback can emit zero-filled
# executables when cwd is the host bind mount and dist is a named volume.
cd /app/dist
Expand All @@ -38,5 +40,5 @@ case "$task" in
exec /app/dist/hack --version
;;
exec) exec "$@" ;;
*) echo "Tasks: models, install, test [paths], check, rust [args], rust-check, config-compiler, build, exec <command>" >&2; exit 2 ;;
*) echo "Tasks: models, install, test [paths], check, rust [args], rust-check, lifecycle-supervisor, config-compiler, build, exec <command>" >&2; exit 2 ;;
esac
8 changes: 7 additions & 1 deletion docker/slim-runtime/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# syntax=docker/dockerfile:1.7

FROM rust:1.97.1-slim-bookworm AS supervisor-build
WORKDIR /src
COPY packages/lifecycle-supervisor ./packages/lifecycle-supervisor
RUN cargo +1.97.1 build --locked --release --jobs 2 --manifest-path packages/lifecycle-supervisor/Cargo.toml --target-dir /build

FROM oven/bun:1.4.4 AS hack-build

WORKDIR /src
Expand Down Expand Up @@ -33,8 +38,9 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*

COPY --from=hack-build /out/hack /usr/local/bin/hack
COPY --from=supervisor-build /build/release/hack-lifecycle-supervisor /usr/local/bin/hack-lifecycle-supervisor

RUN chmod +x /usr/local/bin/hack \
RUN chmod 755 /usr/local/bin/hack /usr/local/bin/hack-lifecycle-supervisor \
&& mkdir -p /var/lib/hack /workspace

ENV HOME=/var/lib/hack
Expand Down
21 changes: 21 additions & 0 deletions docs/guides/prereleases.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,24 @@ exec, run, restart and retaining shutdown, then exercise its browser sign-in,
data-backed pages and assets. Record the bundle version and source commit with
the result. Unit tests, hosted CI and a responding health endpoint do not replace
that application check.

### Lifecycle supervisor packaging

New release archives include `hack-lifecycle-supervisor` beside the CLI. The
standard and slim installers install it with executable mode 0755; Homebrew keeps
it beside `libexec/hack`. Source installs prepare it with
`bun run build:lifecycle-supervisor` (Rust 1.97.1, locked dependencies).
The slim runtime image includes the same sibling executable.

Stable archives build it for macOS arm64/x86_64 and Linux arm64/x86_64. Existing
prerelease bundles remain macOS arm64 only: their supervisor is ad-hoc signed and
covered by the bundle checksums. Retained managers accept older bundles without
this helper. Packaging does not change the lifecycle runner default.

Self-upgrades stage the helper before publishing the CLI, retain its previous
bytes and SHA-256 in the backup, and restore it if publication fails.
Before publishing anything, a self-upgrade asks the staged CLI which helper
handshake it requires and refuses the release, leaving the installation
unchanged, if the staged helper is missing or answers differently. Releases
whose CLI predates the helper are still accepted. Keep the
helper with its matching CLI when moving an installation.
21 changes: 21 additions & 0 deletions docs/versions/v5/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,24 @@ anything, run `hack update --check --json --observe` through the same launcher;
it prints the upgrade command for this install.
For a candidate rollback or return to stable, use
[the full procedure](../guides/candidate-install.md#upgrade-roll-back-and-return-to-stable).

### Lifecycle supervisor packaging

New release archives include `hack-lifecycle-supervisor` beside the CLI. The
standard and slim installers install it with executable mode 0755; Homebrew keeps
it beside `libexec/hack`. Source installs prepare it with
`bun run build:lifecycle-supervisor` (Rust 1.97.1, locked dependencies).
The slim runtime image includes the same sibling executable.

Stable archives build it for macOS arm64/x86_64 and Linux arm64/x86_64. Existing
prerelease bundles remain macOS arm64 only: their supervisor is ad-hoc signed and
covered by the bundle checksums. Retained managers accept older bundles without
this helper. Packaging does not change the lifecycle runner default.

Self-upgrades stage the helper before publishing the CLI, retain its previous
bytes and SHA-256 in the backup, and restore it if publication fails.
Before publishing anything, a self-upgrade asks the staged CLI which helper
handshake it requires and refuses the release, leaving the installation
unchanged, if the staged helper is missing or answers differently. Releases
whose CLI predates the helper are still accepted. Keep the
helper with its matching CLI when moving an installation.
9 changes: 9 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#!/usr/bin/env bun

import {
LIFECYCLE_SUPERVISOR_HANDSHAKE_ARGUMENT,
LIFECYCLE_SUPERVISOR_VERSION,
} from "./src/lib/lifecycle-supervisor-handshake.ts";
import {
finishNativeCpuDiagnostics,
initializeNativeCpuDiagnostics,
Expand All @@ -25,6 +29,11 @@ if (Bun.argv[2] === NATIVE_DETACHED_OWNER_ARGUMENT) {
);
}

if (Bun.argv[2] === LIFECYCLE_SUPERVISOR_HANDSHAKE_ARGUMENT) {
process.stdout.write(`${LIFECYCLE_SUPERVISOR_VERSION}\n`);
process.exit(0);
}

if (Bun.argv[2] === TTY_SUPERVISOR_ARGUMENT && process.send) {
process.exit(await runTtySupervisor());
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dev": "bun run --cwd packages/cli dev",
"build": "bun run --cwd packages/cli build",
"build:local": "sh scripts/build-hack-local.sh",
"build:lifecycle-supervisor": "bun scripts/build-lifecycle-supervisor.ts",
"build:config-compiler": "bun scripts/build-config-compiler.ts",
"check:config-compiler": "cargo fmt --manifest-path packages/config-compiler/Cargo.toml --check && cargo clippy --locked --manifest-path packages/config-compiler/Cargo.toml --target-dir .hack-local/config-compiler-target --all-targets --jobs 2 -- -D warnings",
"test:config-compiler": "bash scripts/test-rust.sh --crate=config-compiler",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
"module": "index.ts",
"scripts": {
"dev": "bun run ../../index.ts",
"build": "cd ../.. && bun build index.ts --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig --no-compile-autoload-tsconfig --no-compile-autoload-package-json --outfile dist/hack",
"build": "cd ../.. && bun scripts/build-lifecycle-supervisor.ts && bun build index.ts --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig --no-compile-autoload-tsconfig --no-compile-autoload-package-json --outfile dist/hack",
"build:release": "cd ../.. && bun run scripts/build-release.ts",
"build:runtime-image": "cd ../.. && bun run scripts/build-node-runtime-image.ts",
"build:node-runtime-image": "cd ../.. && bun run scripts/build-node-runtime-image.ts --variant node-runtime",
"build:slim-runtime-image": "cd ../.. && bun run scripts/build-node-runtime-image.ts --variant slim",
"install:dev": "cd ../.. && bun run scripts/install-cli.ts --mode=dev --force",
"install:dev": "cd ../.. && bun scripts/build-lifecycle-supervisor.ts && bun run scripts/install-cli.ts --mode=dev --force",
"install:bin": "cd ../.. && bun run scripts/install-cli.ts --mode=bin --force",
"install:status": "cd ../.. && bun run scripts/install-status.ts",
"uninstall:local": "cd ../.. && bun run scripts/uninstall-cli.ts",
Expand Down
84 changes: 84 additions & 0 deletions scripts/build-lifecycle-supervisor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bun
import { chmod, copyFile, mkdir, mkdtemp, rename, rm } from "node:fs/promises";
import { join, resolve } from "node:path";
import { verifyConfigCompilerBuildPath } from "./build-config-compiler.ts";

/** Build the host sibling with locked dependencies and bounded Cargo parallelism. */
export async function buildLifecycleSupervisor(): Promise<void> {
const root = resolve(import.meta.dir, "..");
const target = join(root, ".hack-local/lifecycle-supervisor-target");
const output = join(root, "dist/hack-lifecycle-supervisor");
for (const path of [
join(root, ".hack-local"),
target,
join(root, "dist"),
output,
]) {
await verifyConfigCompilerBuildPath({ path });
}
const child = Bun.spawn(
[
"cargo",
"+1.97.1",
"build",
"--locked",
"--release",
"--jobs",
"2",
"--manifest-path",
"packages/lifecycle-supervisor/Cargo.toml",
"--target-dir",
target,
],
{
cwd: root,
stdin: "ignore",
stdout: "inherit",
stderr: "inherit",
timeout: 300_000,
}
);
if ((await child.exited) !== 0) {
throw new Error("Lifecycle supervisor build failed.");
}
const binary = join(target, "release/hack-lifecycle-supervisor");
await verifyConfigCompilerBuildPath({
path: binary,
allowCargoHardLink: true,
});
await mkdir(join(root, "dist"), { recursive: true });
const stage = await mkdtemp(join(root, "dist/.lifecycle-supervisor-build-"));
try {
const staged = join(stage, "hack-lifecycle-supervisor");
await copyFile(binary, staged);
await chmod(staged, 0o755);
if (process.platform === "darwin") {
for (const args of [
["--force", "--sign", "-"],
["--verify", "--strict"],
]) {
const signature = Bun.spawn(["/usr/bin/codesign", ...args, staged], {
stdin: "ignore",
stdout: "inherit",
stderr: "inherit",
timeout: 30_000,
});
if ((await signature.exited) !== 0) {
throw new Error(
"Lifecycle supervisor signature verification failed."
);
}
}
}
// A locally installed helper may still be supervising work. Replace its
// directory entry instead of writing to that running executable's inode.
await verifyConfigCompilerBuildPath({ path: output });
await rename(staged, output);
} finally {
await rm(stage, { recursive: true, force: true });
}
}

if (import.meta.main) {
await buildLifecycleSupervisor();
}
7 changes: 6 additions & 1 deletion scripts/build-native-candidate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ if [ -n "$version" ]; then
else
bun build index.ts --compile --outfile "$out/hack-cli"
fi
bun scripts/build-lifecycle-supervisor.ts
cp dist/hack-lifecycle-supervisor "$out/hack-lifecycle-supervisor"
chmod 755 "$out/hack-lifecycle-supervisor"
/usr/bin/codesign --force --sign - "$out/hack-lifecycle-supervisor"
/usr/bin/codesign --verify --strict "$out/hack-lifecycle-supervisor"
bun scripts/build-config-compiler.ts
cp dist/hack-config-compiler "$out/hack-config-compiler"
cp packages/config-compiler/generated/hack.project.schema.json "$out/hack.project.schema.json"
Expand Down Expand Up @@ -158,7 +163,7 @@ fi
else
shasum -a 256 hack-native hack-relay-guest hack-storage-root-witness native-storage-tool.json hack-cli hack-v5 provider-pins.json README.md > SHA256SUMS
fi
shasum -a 256 hack-config-compiler hack.project.schema.json hack.local.schema.json >> SHA256SUMS
shasum -a 256 hack-lifecycle-supervisor hack-config-compiler hack.project.schema.json hack.local.schema.json >> SHA256SUMS
shasum -a 256 mcp/*/manifest.json mcp/*/hack-mcp-adapter mcp/*/hack-mcp-owner mcp/*/hack-mcp-backend >> SHA256SUMS
)
echo "Candidate bundle: $out"
Expand Down
12 changes: 12 additions & 0 deletions scripts/build-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
renderProjectManagedEnvSchemaJson,
} from "../src/templates.ts";
import { buildConfigCompiler } from "./build-config-compiler.ts";
import { buildLifecycleSupervisor } from "./build-lifecycle-supervisor.ts";

interface BuildArgs {
readonly outDirRaw: string | null;
Expand Down Expand Up @@ -80,6 +81,7 @@ async function main({ args }: { readonly args: BuildArgs }): Promise<number> {
}

await buildConfigCompiler();
await buildLifecycleSupervisor();
const binaryPath = resolve(distRoot, "hack");
const buildExit = await run({
cmd: [
Expand Down Expand Up @@ -112,6 +114,14 @@ async function main({ args }: { readonly args: BuildArgs }): Promise<number> {
});
await chmodExecutable({ path: compilerPath });

await copyFile({
from: resolve(distRoot, "hack-lifecycle-supervisor"),
to: resolve(releaseDir, "hack-lifecycle-supervisor"),
});
await chmodExecutable({
path: resolve(releaseDir, "hack-lifecycle-supervisor"),
});

const assetsDir = resolve(releaseDir, "assets");
const gifsDir = resolve(assetsDir, "gifs");
const schemasDir = resolve(assetsDir, "schemas");
Expand Down Expand Up @@ -390,6 +400,7 @@ export function renderInstallScript(): string {
...installExecutableFunction(),
'mkdir -p "$INSTALL_BIN" "$INSTALL_ASSETS"',
'install_executable "$ROOT/hack-config-compiler" "$INSTALL_BIN/hack-config-compiler"',
'install_executable "$ROOT/hack-lifecycle-supervisor" "$INSTALL_BIN/hack-lifecycle-supervisor"',
'install_executable "$ROOT/hack" "$INSTALL_BIN/hack"',
"",
'if [ -d "$ASSETS_DIR" ]; then',
Expand Down Expand Up @@ -516,6 +527,7 @@ export function renderCodexSlimInstallScript(): string {
...installExecutableFunction(),
'mkdir -p "$INSTALL_BIN" "$INSTALL_ASSETS"',
'install_executable "$ROOT/hack-config-compiler" "$INSTALL_BIN/hack-config-compiler"',
'install_executable "$ROOT/hack-lifecycle-supervisor" "$INSTALL_BIN/hack-lifecycle-supervisor"',
'install_executable "$ROOT/hack" "$REAL_BIN"',
"",
'if [ -d "$ASSETS_DIR" ]; then',
Expand Down
Loading
Loading