`test` chains up -> run -> status -> down, which is the loop you actually want
when verifying a clean install. Three things make it more than a convenience
wrapper:
* It refuses to start against an existing account. A reused home is not a clean
install, so testing one silently would defeat the harness.
* It tears the account down from an EXIT/INT trap armed the moment the account
exists, so a failed run — or a Ctrl-C mid-install — still leaves the machine
clean. BB_TEST_KEEP=1 opts out to poke at a failure.
* Its verdict is stricter than the installer's. install.sh exits 0 when it
finishes but `doctor` reports unmet prerequisites, so "the installer
succeeded" is not a useful assertion; `test` fails if the install fails, if
bot-bottle never reached the new user's PATH, or if doctor is unhappy. That
meant giving cmd_status a real exit status instead of swallowing doctor's.
Also fixes two bugs in `run`'s installer staging, by removing the staging
entirely and feeding install.sh in on stdin:
* `mktemp /tmp/bb-install.XXXXXX.sh` did not do what it looks like. BSD mktemp
only substitutes trailing Xs, so every run wrote the *same* predictable path,
as root, mode 644, in a world-writable directory.
* The cleanup only ran on the normal and failure returns, so an interrupted run
leaked the file.
Piping on stdin sidesteps both: root opens the redirect before sudo drops
privileges, so the mode-700 home that motivated the staging is a non-issue,
there is no file to leak, and `sh -s` matches the documented `curl … | sh`
shape more closely than executing a staged copy did.
The command-level `exit 1`s become `return 1` so the steps compose under the
trap, and the "next, run this" hints are suppressed inside `test`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEfZZhakx13bxTfXcZCoS5
Add scripts/macos-install-test.sh, a throwaway-user harness for exercising
install.sh the way a brand-new user would on macOS, plus the research note
that motivates the approach.
The harness has up/run/status/down/deep-reset subcommands. Because install.sh
writes only to the user home (pipx venv, ~/.bot-bottle, a PATH line) and never
installs the backend, deleting the account is a complete, deterministic reset
of the install surface. A disposable macOS VM can't stand in on M1/M2: the
Apple `container` backend needs Virtualization.framework, and running it inside
a guest VM requires nested virtualization (M3+ only), so a throwaway user is
the only way to reach the real host backend from a clean $HOME.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>