feat(update): self-update — pull, deploy into the launch folder, relaunch (#415) - #417
Merged
Conversation
…unch (#415) ONE service, two front-ends, fail-safe by construction. `keel/commands/update.py` is the whole self-update procedure as orchestration over subprocesses/HTTP (no trading logic); `keel update` (with `--check`, which mutates nothing) and the TUI Account menu's ARMED update view both render the same plan and run the same run. The service's API: - `latest_release(fetch=...)` — the public GitHub releases API, NO auth, no secrets; honest errors for network, rate-limit and unexpected shapes. - `plan_update(release, ...)` — current vs latest (semver, honest on pre-releases/junk), the four production wheel assets, the Release/ dir under the launch folder, every `keel*.db` to back up, the RUNNING venv, offered/ refused with a reason for every refusal. - `run_update(plan, *, echo, confirm_gate, download/install/migrate/verify=...)` — the procedure in the runbook's order, each step streamed, each failure honest. - `relaunch_tui(venv, argv)` — the execv closure (pure construction; only the TUI calls it, terminal restored first). - `gate_action`/`gate_detail`/`typed_update_gate` — ONE gate wording, both front-ends: the CLI's own `_require_interactive_confirmation`, failing closed off a TTY. The fail-safe contract: the confirm gate runs INSIDE `run_update` (no ungated path to the writes exists); timestamped `.bak-before-<version>-<ts>` backups of every `keel*.db` land BEFORE anything installs (pinned by a test that asserts the backups from inside the install seam); the four production wheels only — never fake/robinhood (RELEASING.md's rule, hard-coded by prefix); install by path into the RUNNING venv via uv; `keel migrate --db` each database with the NEW build; verify with the new venv's `keel versions` (every distribution at the target); only a verified success deletes the superseded wheels. A failed verify says the real state loudly (pip has already replaced the packages), re-installs the previous wheels best-effort when they remain in Release/, and names the manual recovery. Backups are never deleted. Never automatic — always typed. Dev/source checkouts are refused: not a release build, nothing installed, or the running `keel` package resolving from the launch folder itself. uv is a deployment dependency of self-update (the runbook's own installer); an absent uv is an honest error naming the manual procedure. The ceremony table gains the Account update row (TYPED class; the branch's one write path). The thinness pin gains Rule 5: no subprocess/urllib/execv in the console layer at all — the orchestration lives in the service, so no allowance was needed. The TUI relaunches itself via execv on success; the CLI prints the command and does not relaunch. Runbook: "Self-update" under "Deploying a new version", cross-linked with the console section. Fixes #415 Signed-off-by: Elmehdi Aitbrahim <eaitbrahim@gmail.com>
…v, snapshot backups, phase-true failure text Twelve adversarial-review findings on #415's self-update: - [BLOCKER] the dev-checkout refusal refused every REAL deployment (its package lives at <launch>/.venv/lib/.../site-packages/keel/). The detection is now deployment-true: an update is offered only when the running package resolves from the launch folder's OWN .venv site-packages; a non-venv path under the launch folder (a source keel/ dir), a package outside the launch folder (a repo run), or a keel-trader origin that is not a wheel (direct_url.json) is refused, each naming the layout it saw. - [BLOCKER] build_relaunch_argv keeps the original argv VERBATIM after argv[0] (wrappers exec 'keel --config X --db Y tui'; the old fallback prepended tui and produced a duplicate subcommand); [keel, tui] only for a bare wrapper. - backups are consistent SQLite snapshots (sqlite3 online-backup API), not copy2 of a live db under a rollback journal; a same-second backup name gets a counter suffix instead of overwriting. - failure text is phase-true: a failed INSTALL says the venv was NOT updated (or is half-updated -- run keel versions), never claims the wheels are in, and removes the downloaded (possibly torn) wheels from Release/; only a finished install gets the loud wheels-ARE-installed text, the best-effort reinstall, and the backups named as the data recovery (the additive- migration belief stated as an assumption). - a relaunch execv that raises renders the manual 'keel tui' start, holds the result (no silent re-ARM), and Enter retries ONLY the relaunch. - the ungated-callable overclaim is gone (both shipped front-ends gate; the API is callable by an operator's own code); the download read is bounded at 200 MiB; ambiguous wheel assets are refused naming both; uv install carries --find-links Release exactly like the runbook; the runbook counts the per-DB migrate step the four commands skip.
This was referenced Aug 19, 2026
Merged
eaitbrahim
added a commit
that referenced
this pull request
Aug 20, 2026
Minor, not patch. Three things since v0.9.3 change what an implementor or a deployment can rely on: * A NEW DISTRIBUTION. `keel-broker-alpaca` (#382, #384) plus the paper-equities profile that selects it (#386), so a deployment can now be US equities via the broker port rather than crypto only. * THE PORT CONTRACT MOVED TWICE. `market_clock`/`market_schedule` made venues session-aware (#385), and `place_order` gained `idempotency_key` (#419). Both carry defaults so no CALLER breaks, but a third-party adapter that does not accept them is no longer a `Broker` -- the conformance suite now says so. That is exactly the kind of change a patch bump must not hide. * THE OPERATOR CONSOLE. The TUI became keel's console across #399-#408, and `keel update` (#415/#417) makes a deployment self-updating. Every pinned sibling moves with it. The four production distributions are required `==` at this exact version (`RELEASING.md`, "Release assets"), so a bump that missed one would install a mixed set -- the `keel-trader 0.5.7` against `keel-core 0.5.5` failure `keel versions` exists to catch, and which `~/keel` actually ran across two releases. Also in this window, on the Robinhood adapter: the best_bid_ask fixture corrected against the live venue (#414), a credential guard that catches the error it only claimed to (#416), pre-flight sizing reported on the preview (#418), transport backoff (#420), and the fenced one-order probe (#421). Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The console keeps the deployment current (#415):
keel update(CLI) and the TUI Account menu's update entry — one service, two front-ends, fail-safe by construction.keel/commands/update.pyis pure orchestration over subprocesses/HTTP; both front-ends render the same plan (render_plan_lines), run the same run (run_update), and ask the same typed gate (typed_update_gate— the CLI's own_require_interactive_confirmation, failing closed off a TTY).latest_release(fetch=...)— the public GitHub releases API, no auth, no secrets; honest errors for network, rate-limit and unexpected payloads.plan_update(release, ...)— current vs latest (semver; a release supersedes its own pre-release, junk tags refuse rather than guess), the four production wheel assets, theRelease/dir under the launch folder, everykeel*.dbto back up, the RUNNING venv, and offered/refused with a reason for every refusal.run_update(plan, *, echo, confirm_gate, download/install/migrate/verify=...)— the runbook's manual procedure, in its order, each step streamed, each failure honest.relaunch_tui(venv, argv)— the execv closure (pure construction; the new venv'skeelentry carrying the original TUI argv, falling back to[keel, tui]).The fail-safe contract
run_updatetakesconfirm_gateand calls it before any mutation: there is no ungated path to the writes. Never automatic, always typed, fails closed off a TTY.keel*.dbin the launch folder is copied to<db>.bak-before-<version>-<ts>before anything downloads or installs (pinned by a test that asserts the backups exist from inside the install seam), and backups are never deleted.keel_core,keel_broker_api,keel_broker_coinbase,keel_traderby exact name, neverRelease/*.whl: the fake and Robinhood wheels can never ride along (RELEASING.md's rule).keel migrate --dbeach database with the new build.keel versionsmust report every keel distribution at the target; only a verified success removes the superseded wheels.Release/(they do — cleanup only happens on success), and names the manual recovery.Refusals and the relaunch split
Dev/source checkouts are refused (not a release build; nothing installed; or the running
keelpackage resolving from the launch folder itself — theuv run keelcase). The TUI relaunches itself on a verified success (os.execvthe new build's entry, terminal restored first, original TUI argv carried); the CLI prints the command and does not relaunch anything.The audit surfaces
Red-first throughout: the service's pure parts,
run_updateagainst a fake environment (ordering, verify-failure loudness + best-effort reinstall, superseded-cleanup-only-on-success, backups-never-deleted, the no-mutations--checkspy, the closed-gate refusal), the relaunch closure, the TUI ARMED/typed/relaunch paths, the ceremony row, and the runbook pins all failed before the implementation landed.Fixes #415