Skip to content

feat(update): self-update — pull, deploy into the launch folder, relaunch (#415) - #417

Merged
eaitbrahim merged 2 commits into
mainfrom
feat/415-self-update
Aug 19, 2026
Merged

feat(update): self-update — pull, deploy into the launch folder, relaunch (#415)#417
eaitbrahim merged 2 commits into
mainfrom
feat/415-self-update

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

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.py is 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, the Release/ dir under the launch folder, every keel*.db to 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's keel entry carrying the original TUI argv, falling back to [keel, tui]).

The fail-safe contract

  • The gate runs inside the servicerun_update takes confirm_gate and calls it before any mutation: there is no ungated path to the writes. Never automatic, always typed, fails closed off a TTY.
  • Backups first — every keel*.db in 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.
  • The four production wheels onlykeel_core, keel_broker_api, keel_broker_coinbase, keel_trader by exact name, never Release/*.whl: the fake and Robinhood wheels can never ride along (RELEASING.md's rule).
  • Install by path into the RUNNING venv via uv (uv is a deployment dependency of self-update; an absent uv is an honest error naming the manual procedure), then keel migrate --db each database with the new build.
  • Verify before cleanup — the new venv's keel versions must report every keel distribution at the target; only a verified success removes the superseded wheels.
  • A failed verify is loud, never papered over — pip has already replaced the packages, so the updater says exactly that state, re-installs the previous wheels best-effort while they remain in 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 keel package resolving from the launch folder itself — the uv run keel case). The TUI relaunches itself on a verified success (os.execv the new build's entry, terminal restored first, original TUI argv carried); the CLI prints the command and does not relaunch anything.

The audit surfaces

  • Ceremony table: the Account update row (TYPED class) — the branch's one write path; its old "no mutating actions" pin became "exactly one, the typed update".
  • Thinness pin: a new Rule 5 — no subprocess/urllib/execv anywhere in the console layer (the orchestration is the service's, which the pin doesn't scan), so no allowance was needed; the rule carries its own positive control.
  • Runbook: "Self-update" under "Deploying a new version" (the procedure it automates), cross-linked with the console section; the typed-contract count and the Account-menu read-only claims updated to stay true.

Red-first throughout: the service's pure parts, run_update against a fake environment (ordering, verify-failure loudness + best-effort reinstall, superseded-cleanup-only-on-success, backups-never-deleted, the no-mutations --check spy, 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

…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.
@eaitbrahim
eaitbrahim merged commit 5af0b8a into main Aug 19, 2026
5 checks passed
@eaitbrahim
eaitbrahim deleted the feat/415-self-update branch August 19, 2026 21:54
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-update: pull the latest release, deploy into the launch folder, relaunch the TUI

1 participant