14 Commits

Author SHA1 Message Date
ethernet
b728fdd627 refactor(pm): own the lock/atomic-write primitives in pm.filesystem
pm imported runtime_state's private helpers (_lock, _atomic_bytes, _bytes,
_digest) at a dozen sites while runtime_state imports pm.environments at
module top. The primitives are pm's: move them into the stdlib-only
pm.filesystem as lock_fd, durable_write_bytes, read_bytes_or_none and
file_digest, and repoint every pm caller.

runtime_state keeps the private names only as import aliases: it still
calls them through its own globals, and pre-PM updaters load them by these
names mid-swap (tests/compat/old_updater_surface.json).

Boot-subset test fixtures now copy pm/filesystem.py, since runtime_state
imports it at process boot; worker-injection tests patch the name
pm.publication now reads.
2026-09-24 14:08:03 -04:00
ethernet
97b0ab42e8 fix(pm): flush a generation tree before its selection record names it
The selection record (the install's facts.json, a side environment's
active.json) is already written last, atomically and fsynced, so it is the
generation's completion marker. But nothing flushed the tree it vouches for
first: a power loss mid-publish could persist the record while the venv's
files were still in the page cache, and the next boot would select a
half-written environment whose pyvenv.cfg happened to exist.

Both generation publishers (Venv via sync_venv, and _ensure_generation for
side environments) now call pm.environments.flush_before_selecting() after
the tree is complete and before the record is written: one os.sync() rather
than an fsync per file, which would cost minutes for a venv. Windows has no
os.sync; there the atomic record is the only guarantee, documented in place.

Reusing the existing record instead of adding a new marker file means no
migration: generations published by earlier builds stay selectable, and no
reader needs a legacy rule. Every reader of the selection (selected_venv,
environment_python, and their callers) already requires the record.
2026-09-24 11:50:30 -04:00
ethernet
7b68fa7700 fix(pm): rebuild side environments when the selected interpreter moves
A venv records its base interpreter path in pyvenv.cfg, not just a version.
The generation identity hashed only the pinned Python artifact, so the same
pin realized in a different tools store (a fresh HERMES_RUNTIME_DIR, a CI
setup-pm store) reused a venv whose interpreter no longer existed or belonged
to another store. Key the identity on the resolved selected interpreter and
build against it explicitly; a failed replacement still keeps the prior
generation selected.
2026-09-24 10:42:43 -04:00
ethernet
33754a37e0 fix(pm): use explicit text encodings in touched paths 2026-09-24 01:31:40 -04:00
ethernet
dcd2bca06a fix(desktop): render icons with core runtime dependencies 2026-09-24 01:30:15 -04:00
ethernet
babbec1c4c feat(pm): isolate developer test environment from runtime extras 2026-09-23 18:13:38 -04:00
ethernet
c98bdb77f5 fix(pm): prepare the Windows ARM64 compiler environment for every source dependency build
cryptography ships no win_arm64 wheel, so every Windows ARM64 venv sync
compiles it from the sdist and needs MSVC, Clang, Rust and static OpenSSL.
Only setup-hermes.ps1 (and so activate.ps1) prepared that environment,
between a `pm install --tools-only` and the real sync. install.ps1,
`hermes update` and repair ran the same sync without it and failed in
openssl-sys.

PM owns the sync, so PM prepares it. pm/native_build.py holds the adapter
(moved from scripts/build/windows_deps.py) plus source_build_environment(),
which prepares only on win32-arm64 when the synced project carries the
provider script. A payload has prebuilt dependencies and needs no compiler.
VenvPackage.apply and build_environment pass the result to uv children
only. It carries the bridged pip index settings, which managed_environment
applies only to the ambient environment. The state root stays the store
parent, so existing vcpkg/OpenSSL builds are reused.

setup-hermes.ps1 collapses to one `pm install`: the tools-only split existed
only for this preparation, and pm install already puts its tools on PATH
before the venv sync (pm/cli.py activate check).

Not yet verified live on Windows ARM64.
2026-09-23 16:07:47 -04:00
ethernet
6a5a6a05d2 refactor(pm): rename the pm.ensure submodule to pm.install; lazy_deps back to the shim
`import pm.ensure` bound the submodule onto the package, shadowing the facade's
`pm.ensure()` function for every later caller in the process (photon's sidecar
start hit `'module' object is not callable`). The module is pm.install now; the
function keeps its name. The facade resolves through `__import__` rather than
`importlib.import_module` so a test that patches import_module globally does not
break attribute access on pm.

tools/lazy_deps.py returns to the 16-line stop_for_relaunch shim the branch wrote
(an origin/main merge had replaced it with main's 775-line implementation); the
project-metadata tests follow. update_cmd re-exports the four old_updater_deps
names the shim tests resolve through hermes_cli.update_cmd.
2026-09-18 23:27:05 -04:00
ethernet
bbec973514 refactor(pm): pm owns the dependency-environment layout and interpreter paths
hermes_cli.runtime_paths (venv generations, selection, activation) moves to
pm.environments, and gains venv_bin_dir / venv_python / project_python. Every
in-tree caller asks pm for an interpreter now; pm no longer reaches back into
hermes_cli for its own environment layout (pm.packages, pm.extras, pm.ensure,
pm.paths imported hermes_cli.runtime_paths). The three open-coded
"Scripts/python.exe or bin/python" ladders in pm collapse onto venv_python.

hermes_constants.venv_python_path / venv_bin_dir and hermes_cli.runtime_paths
stay as frozen-updater-surface shims only (tests/compat/old_updater_surface.json).

To keep the boot path light, pm/__init__ resolves its facade lazily (PEP 562)
and pm.registry loads the built-in package definitions on first read instead of
at import: `import hermes_bootstrap` now loads pm + pm.environments only (25ms,
was 37ms with the eager facade dragging in the downloader). The stripped-payload
fixtures that ship only pre-import files keep working for the same reason.

Also restores two frozen-surface re-exports the F401 sweep dropped
(banner._github_compare_behind, cua_backend.resolve_cua_driver_cmd).
2026-09-18 20:02:36 -04:00
ethernet
53e6f001c7 refactor(pm): consolidate runtime ownership and updater completion
Run historical updater completion in a fresh interpreter so cached imports
cannot revive retired dependency installers. Share Git and ZIP completion,
carry receipt and recovery state, and preserve child exit status.

Route plugin admission, binary acquisition, desktop launch and build paths
through PM. Replace redundant helpers and tests with real worker, package,
publication and launch checks. Keep the shipped compatibility surface fixed.

Targeted Python and desktop checks pass. Native update journeys and fresh
production image qualification remain pending. This is a checkpoint before
those acceptance runs.
2026-09-12 16:30:35 -04:00
ethernet
8b56a9306f refactor(pm): own group-only builds and finish dependency hints 2026-09-11 18:25:05 -04:00
ethernet
cdd76e03ec fix(pm): enforce install policy for explicit Python builds 2026-09-11 18:13:56 -04:00
ethernet
c7b1f238b5 fix(pm): keep bootstrap and readiness checks behind safe operations 2026-09-11 18:11:59 -04:00
ethernet
cc48185220 refactor(pm): expose Python operations instead of uv binaries 2026-09-11 18:05:28 -04:00