Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
126 changes: 97 additions & 29 deletions specs/HIVE-267-upgrade-swap/proposal.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
id: "HIVE-267-upgrade-swap"
type: spec
status: draft # draft | implementing | verifying | archived
status: verifying # draft | implementing | verifying | archived
created: "2026-06-24"
issue: "hive#267" # repo#NNN — GitHub issue / Project item that tracks this spec
tags: [spec, proposal]
Expand All @@ -12,10 +12,24 @@ template_version: "1.0"

> **Naming**: file lives at `<repo>/specs/HIVE-267-upgrade-swap/proposal.md`.

> `[AGENT-DRAFT — review before archive]` — this proposal was drafted from the
> rich content of issue #267. The blocking mechanism decision is **RESOLVED**
> (A3-first, see Risks); the remaining sections are drafted from the issue —
> review them before archive.
> **Reviewed 2026-08-07 (#333); all draft markers resolved.** The A3 mechanism
> shipped in #290, #294 and #302 (`src/hive/_runtime.py`, 25 tests in
> `tests/test_runtime.py`) and is depended on in production by
> `_service._current_layout_exec()` and [ADR-019](../../docs/adr/adr-019-launcher-ownership.md).
>
> **Status is `verifying`, not `archived`, for two concrete reasons** — see
> Acceptance criteria:
>
> 1. **AC1/AC2 cannot be verified end-to-end yet.** They require `hive --version`
> to report the new version from a shell, and no launcher is on `PATH` — this
> spec's `tasks.md` promised "+ own launcher" and it was never built. That gap
> is [#328](https://github.com/mlorentedev/hive/issues/328); its PR1 shipped
> (#330) and ADR-019 settled the ownership question, but PR2 (the launcher
> itself) has not landed.
> 2. **AC4 (real non-admin Windows re-validation) has not been performed.** The
> A3 *feasibility spike* passed on real hardware 2026-06-24, but the shipped
> implementation was never re-validated there, and that box is currently in the
> orphaned-trampoline failure state (dotfiles#791).

## Why

Expand All @@ -34,16 +48,36 @@ primary vault-access surface.

## What

`[AGENT-DRAFT]` A swap mechanism in hive that lets the install be replaced
**without ever touching in-use files**, so an upgrade applied while the daemon is
running leaves a valid install instead of a locked, malformed one. After this
change, an upgrade-while-running ends with the `hive` entrypoint present and
`hive --version` reporting the new version; a failed swap leaves the previous
working install intact rather than a half-removed directory.
A swap mechanism in hive that lets the install be replaced **without ever
touching in-use files**, so an upgrade applied while the daemon is running leaves
a valid install instead of a locked, malformed one. After this change, an
upgrade-while-running ends with the `hive` entrypoint present and `hive --version`
reporting the new version; a failed swap leaves the previous working install
intact rather than a half-removed directory.

**Shipped** as `src/hive/_runtime.py` — `runtime_root()`, `versions_dir()`,
`version_path()`, `current_link()`, `_make_junction()`, `repoint()`,
`build_version()`, `current_version()`, `remove_version()`, `latest_version()`,
`_gc_other_versions()`, `self_upgrade()` — plus the `hive self-upgrade [version]`
subcommand (`server._run_self_upgrade`). PRs #290, #294, #302.

**One clause of the "After this change" sentence is still unmet:** `hive
--version` cannot be invoked from a shell, because nothing installs a launcher on
`PATH`. The layout is correct and `current` resolves to a working venv; it is
simply unreachable by name. Tracked as [#328](https://github.com/mlorentedev/hive/issues/328).

## Out of scope

`[AGENT-DRAFT]` — confirm the boundary, especially the cross-repo split.
**Boundary confirmed 2026-08-07.** The cross-repo split held up in practice: the
hive side shipped independently, and the dotfiles side is tracked separately as
AI-028 (dotfiles#791). One item has since been carved out into its own hive spec
rather than staying here:

- **The PATH launcher** — promised by this spec's `tasks.md` ("hive owns the layout
… **+ own launcher**") but never built. Now owned by
`specs/HIVE-328-runtime-launcher/` and decided by
[ADR-019](../../docs/adr/adr-019-launcher-ownership.md). Keeping it here would
have left two specs claiming the same deliverable.

- The **dotfiles rollout** that triggers the upgrade (`setup-windows.ps1`
supervision/upgrade block, `tests/hive-upgrade-timer.bats`, `mcp-servers.json`
Expand Down Expand Up @@ -96,26 +130,60 @@ working install intact rather than a half-removed directory.
`uv tool list` shows a stale/unmanaged entry; documented as expected. (An
upstream `uv` issue — replace-while-running on Windows — is filed regardless,
per ADR-015.)
- `[AGENT-DRAFT]` **The supervisor holds the lock.** The swap must succeed while
the Startup supervisor keeps `python.exe` running, or coordinate a bounded
stop/restart (exit-75 contract) without a window where the MCP is dead.
- `[AGENT-DRAFT]` **Windows-only, hardware-gated.** Like the other ADR-015
mechanisms, this needs validation on a real non-admin Windows box, not just CI.
- **RESOLVED — the supervisor holding the lock is a non-issue for A3 (spike,
2026-06-24; design confirmed at implementation).** This was the risk A3 was
chosen to dissolve rather than manage: junction operations touch only the reparse
point, never the locked target files, so the repoint succeeds *while* the
supervisor keeps `python.exe` running. The spike proved it directly — `current`
was repointed from `versions/1.41.5` to `1.41.6` while an exclusive
`FileShare.None` lock was held on `1.41.5/core.pyd`, with no "Access is denied".
No bounded stop/restart is needed, so there is no window where the MCP is dead.
`self_upgrade` deliberately does **not** restart the daemon; the supervisor's
pre-existing exit-75 restart-on-upgrade contract relaunches `hive serve` through
the freshly repointed `current`. GC of a still-locked old version returns `False`
and defers to the next run rather than failing the upgrade.
- **RESOLVED as scoping, NOT as validation — Windows-only is confirmed; the
hardware re-validation is still owed.** Windows-only is settled and no longer
open: A3 exists solely to work around Windows' in-use-file lock, and POSIX has no
such constraint, so **Linux keeps `uv tool`** (dotfiles AI-028 AC6). ADR-019
reaches the same conclusion for the launcher.
**But the hardware gate itself is unmet.** What passed on real hardware was the
A3 *feasibility spike*, before any code existed; the shipped `_runtime.py` has
only ever run on CI (Linux/Windows runners) and this Linux dev box. See AC4 — it
is one of the two reasons this spec is `verifying` rather than `archived`, and it
is blocked in practice because the target machine is in the orphaned-trampoline
state (dotfiles#791).

## Acceptance criteria

`[AGENT-DRAFT]` Observable outcomes. Each must be testable.

- [ ] An upgrade applied **while the daemon is running** leaves a valid install:
the `hive` entrypoint is present and `hive --version` reports the new
version — no malformed/locked state.
- [ ] The documented #267 reproduction (`uv tool install --upgrade` against a
running daemon) no longer fails with "Access is denied / failed to remove
directory" on in-use files.
- [ ] A swap that cannot complete leaves the **previous** working install intact
and surfaces an actionable error — never a silently corrupted, dead MCP.
- [ ] Validated on a real non-admin Windows box (ADR-015 hardware-validation
discipline), not only the CI matrix.
Observable outcomes. Each must be testable. Status reviewed 2026-08-07 — marked
against evidence, not against intent.

- [~] **AC1 — an upgrade applied while the daemon is running leaves a valid
install:** the `hive` entrypoint is present and `hive --version` reports the
new version, with no malformed/locked state. **Partially met.** The layout
half holds: `build_version()` writes beside the in-use dir and `repoint()`
flips the junction, so the entrypoint exists at
`current/Scripts/hive.exe` — asserted by `tests/test_runtime.py`. The
`hive --version` half **cannot be verified**, because no launcher is on
`PATH` ([#328](https://github.com/mlorentedev/hive/issues/328)).
- [~] **AC2 — the documented #267 reproduction no longer fails on in-use files.**
**Met in mechanism, unverified in the field.** The spike reproduced the exact
failure mode and showed the repoint succeeding under an exclusive lock
(`verification.md`). Not re-run against the shipped code on real hardware —
same gap as AC4.
- [x] **AC3 — a failed swap leaves the previous working install intact and
surfaces an actionable error.** **Met.** Stage-then-flip: the fallible
`_make_junction` runs before `current` is disturbed, and failure raises a
WHY/FIX `RuntimeError`. `build_version()` cleans a half-built dir on failure;
`remove_version()` refuses the active version. Test:
`test_failed_repoint_leaves_the_previous_current_intact`.
- [ ] **AC4 — validated on a real non-admin Windows box** (ADR-015
hardware-validation discipline), not only the CI matrix. **NOT met.** The
2026-06-24 real-hardware pass was the *feasibility spike*, before any code
existed. The shipped implementation has run only on CI and a Linux dev box.
Blocked in practice: the target machine is in the orphaned-trampoline state
(dotfiles#791).

## References

Expand Down
43 changes: 31 additions & 12 deletions specs/HIVE-267-upgrade-swap/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ created: "2026-06-24"
# Tasks - HIVE-267-upgrade-swap

> TDD order. One task = one focused commit. Tick as you go. Reorder freely while spec is in `draft` state; freeze once you start `implementing`.
>
> **FROZEN — spec is `verifying` (reviewed 2026-08-07, #333).** The A3 mechanism
> shipped in #290, #294 and #302. Two items remain, both outside this repo's
> unit-test reach: the dotfiles companion trigger, and real-hardware
> re-validation. See `proposal.md` -> Acceptance criteria for what is and is not
> met.

## Setup

- [x] Branch created from main: `feat/HIVE-267-upgrade-swap`
- [ ] `proposal.md` is complete and acceptance criteria are testable
- [ ] No open questions left in `proposal.md` "Risks / open questions"
- [x] `proposal.md` is complete and acceptance criteria are testable — reviewed 2026-08-07, marked against evidence
- [x] No open questions left in `proposal.md` "Risks / open questions" — all draft markers resolved 2026-08-07 (#333)

## Implementation

Expand All @@ -25,7 +31,8 @@ created: "2026-06-24"
succeeded on release. Evidence in `verification.md`. **A3 confirmed feasible
— proceed; no fallback to A4/A2 needed.**
> Interposition = **hive owns the layout** (`%LOCALAPPDATA%\hive\runtime\versions\<v>`
> + `current` junction + own launcher; versions built via `uv venv` + `uv pip
> + `current` junction + own launcher **[moved to HIVE-328 / ADR-019 — never
> built here, see the correction below]**; versions built via `uv venv` + `uv pip
> install`; dotfiles upgrade calls `hive self-upgrade`). Likely a new
> `src/hive/_runtime.py` (layout/junction/GC) + a `hive self-upgrade` subcommand.

Expand Down Expand Up @@ -55,6 +62,15 @@ created: "2026-06-24"
trigger. The launcher (`~/.local/bin`) + supervisor already resolve through
`current` (repoint primitive). Tracked as successor issue #292. Tests in
`tests/test_runtime.py` (`self_upgrade` orchestration + CLI wrapper).
**CORRECTION (2026-08-07, #333):** this task claimed "The launcher
(`~/.local/bin`) + supervisor already resolve through `current`". The
supervisor half is true; **the launcher half was false** — nothing ever
installed one, so after a successful upgrade `current` pointed at a
working venv no shell could find. That false claim is why the "+ own
launcher" promise below went unnoticed, and it became
[#328](https://github.com/mlorentedev/hive/issues/328). `~/.local/bin` is
also the wrong directory: [ADR-019](../../docs/adr/adr-019-launcher-ownership.md)
chose `%LOCALAPPDATA%\hive\bin` and forbids hive touching `~/.local/bin`.
- [x] **Auto-latest resolution (#292):** make `hive self-upgrade`'s version
argument optional — omitted, it resolves the newest `hive-vault` release
from PyPI's JSON API (`_runtime.latest_version`, the module's single
Expand All @@ -66,18 +82,21 @@ created: "2026-06-24"
--upgrade` trigger (`setup-windows.ps1` timer / `mcp-servers.json`
`prerequisite_command`) with `hive self-upgrade` (now bare — auto-latest
landed above). Document the "no-longer-a-uv-tool on Windows" consequence.
- [ ] Real-hardware re-validation: the #267 reproduction no longer reproduces
(manual, recorded in `verification.md`).
- [ ] **Real-hardware re-validation (AC4, gating archive):** the #267
reproduction no longer reproduces against the *shipped* code (manual,
recorded in `verification.md`). The 2026-06-24 hardware pass covered the
feasibility spike only. Blocked in practice — the target Windows box is in
the orphaned-trampoline state (dotfiles#791).

## Closing

- [ ] Every acceptance criterion from `proposal.md` is covered by at least one test
- [ ] Every acceptance criterion has a matching entry in `features.json` with a non-vacuous verification command
- [ ] Type checks pass
- [ ] Lint passes
- [ ] No unrelated changes in the diff (no scope creep)
- [ ] `verification.md` filled in
- [ ] PR opened referencing this spec folder
- [~] Every acceptance criterion from `proposal.md` is covered by at least one test — AC3 fully; AC1/AC2 at unit level only (the end-to-end half needs #328's launcher); AC4 is inherently manual
- [x] Every acceptance criterion has a matching entry in `features.json` with a non-vacuous verification command — 5 features, all `pending`; the two manual ones say so explicitly
- [x] Type checks pass — `mypy --strict src/` clean
- [x] Lint passes — `ruff check` + `ruff format --check` clean
- [x] No unrelated changes in the diff (no scope creep)
- [x] `verification.md` filled in — evidence recorded 2026-08-07
- [x] PR opened referencing this spec folder — #290, #294, #302

## Machine-readable features

Expand Down
Loading
Loading