Commit Graph

61 Commits

Author SHA1 Message Date
ethernet
8f6d98e4c3 fix activation of devenv, use /usr/bin/env bash everywhere 2026-09-11 11:17:18 -04:00
ethernet
e86d31fade fix(pm): refresh artifacts within the same minor version
BtbN publishes new FFmpeg builds without changing the version number.
The shared-minor comparison therefore reported stale artifacts as current.

Compare advertised artifact URLs during resolution and hash changed URLs
when applying the update. Keep dry-run checks metadata-only and preserve
pins for targets without an update source, including Termux.

Verification: 41 focused tests passed. The regression exercises real
archive downloads, installation, retained target pins, and a second
update that performs no writes. Native ARM64 FFmpeg also passed a real
16 kHz audio encode after installation.
2026-09-11 09:24:18 -04:00
ethernet
c8a9682505 fix(pm): preserve pinned binary inputs in R2
Termux removes old package files, so a pinned URL and hash do not keep
build inputs available. Preserve the exact bytes without changing pins.

Archive every PM HTTP artifact and the Termux runtime inputs by SHA256.
CI reads R2 first. Only a missing object permits an upstream download,
hash verification, immutable upload, and verified readback. Seed the
actual toolchain and payload stores before their consumers run.

Use the public archive as a pinned fallback in PM, bootstrap installers,
and Nix fetchers. Keep network retries bounded and report attempted URLs.
Keep publication credentials in protected CI jobs, not installed clients.

Verification:
- 283 targeted tests passed; five POSIX tests skipped on Windows.
- All 87 preserved Termux packages passed local archive miss/hit checks.
- Native ARM64 ripgrep installed through the mirror and ran successfully.
- Wheel import, workflow lint, Python lint, shell syntax, and pins passed.

Live R2 publication, POSIX tests, and Nix builds remain for native CI.
The real-byte archive checks used loopback HTTP, not the live bucket.
2026-09-10 18:17:08 -04:00
ethernet
90482e16b9 Merge branch 'ethie/pm-clean' of github.com:NousResearch/hermes-agent into ethie/pm-clean 2026-09-10 16:09:14 -04:00
ethernet
949a508308 fix(pm): remove download archives after package publication
Retain archives through extraction, verification and publication so failed
or paused installs can retry. After success, delete only the package's
archives while holding the store lock. Normal installs commit facts first.
Cross-target staging follows the same cleanup rule.

Keep unrelated archives and resumable partials. Docker uses this PM behavior
without a separate cleanup command. Later repairs may need a new download.

Verified 64 focused PM tests and 9 Docker tests. Failure-injection tests
cover extraction, verification, publication and facts writes. The rebuilt
linux/amd64 image contains no archives in any layer and remains 1.073 GB
compressed. Real Chromium navigation, clicks and screenshots pass.
2026-09-10 16:08:03 -04:00
ethernet
6756d11b5f fix(pm): ship full chromium without headless shell
Full Chromium serves both headed and headless sessions. The separate
shell duplicates the browser payload and is not needed for either mode.

Remove the shell from PM and Docker. Select the managed Chromium
executable for agent-browser and the full Chromium channel for direct
Playwright callers. Route setup through PM and remove retired packages
from cached bundle stores without changing the user's tool store.

Update signing, architecture checks, launch probes and install guidance.
Leave llama packages and Docker archive cleanup unchanged.

Verification:
- Real agent-browser navigation, clicks, DOM reads and screenshots pass
  in headed and headless modes with the same Chromium executable.
- The direct Playwright doctor probe passes.
- Focused Python and desktop packaging tests pass, as do six Docker
  checks and both real-browser task-scroll tests.
- The built linux/amd64 image is 1.393 GB compressed, 223.6 MB smaller.
- The broader PM suite and two unrelated setup tests still fail.
  Those failures reproduce on unchanged HEAD.
- Five updated eval scripts parse; their full scenarios were not run.
2026-09-10 16:08:03 -04:00
ethernet
f5981cbd14 fix(desktop): preserve MSIX runtime and add Store updates
Use a verified writable copy of pinned Python for uv builds. Keep the
signed launchers and bundled Python as the MSIX runtime so plugin
rebuilds do not remove package access.

Identify Microsoft Store builds from the artifact stamp. Use StoreContext
for update checks, downloads, and installation requests inside Hermes.
Keep relaunch and recovery ownership across the update. Do not report
unknown checks or no-op requests as successful installations.

Open the build commit link in the system browser.

Verification: 168 Python tests, 107 Electron tests, and 121 renderer tests
passed. Typechecks, lint, lock validation, and desktop builds passed.
A real packaged process completed plugin admission, import, and repair.

Store-acquired download, installation, and relaunch remain unverified.
This host has only the sideloaded MSIX. Its real Store API call returned
an error, which the app reports as unknown. macOS updates are unchanged.
2026-09-10 15:17:18 -04:00
ethernet
b2be572937 fix(pm): refresh dependencies with the installed project tools
The uv step used a nonexistent command. Both dependency steps used the
caller directory instead of the PM repository. Run uv lock --upgrade
and the installed npm executable in the correct project. Require the
installed tool closure without installing a fallback.

Reuse npm environment sanitization for unpack and update. The separately
pinned npm keeps its Node dependency on PATH without changing the parent.
Missing tools and failed commands return failure before venv sync.

Real uv and npm commands ran in guarded temporary projects. The test
removes Node's bundled npm before update and preserves unrelated files.
The missing-Python test checks the actual refusal and unchanged facts.
All 87 focused tests pass. Lint passes. No project pins or locks changed.
2026-09-10 03:50:40 -04:00
ethernet
e9bf6d97c2 fix(pm): select the Python identity advertised by its asset
The resolver combined the locked patch with a newer release tag.
That combination did not name the archive advertised by the release.
Return the complete identity from an exact matching filename instead.

Keep the locked minor and reject free-threaded or mismatched-tag assets.
The URL builder supplies its existing target mapping to the resolver.
Bionic remains a manual source. No version pins change in this commit.

Verified: 47 tests passed. The real package caller reconstructs each
accepted asset name, with negative controls for lookalikes and tag skew.
A fresh upstream metadata snapshot agrees with all six PBS target URLs.
No interpreter artifacts were downloaded or installed.
2026-09-10 02:40:54 -04:00
ethernet
8afc241e6e fix(pm): fail updates when package resolution fails
A failed lookup was reported as no change and returned success.
Track failures where exceptions occur rather than parsing status text.
Report independent successful lookups, then stop before any pin,
install or dependency refresh if one lookup failed.

Verification: the real PM CLI exercises check and apply with failed,
mixed, current and manual-source results. Mutation boundaries stay
unreached and the temporary lock stays unchanged. The focused gate
passed 36 tests with no failures. Lint passed.
No upstream package, real pin table or installed environment changed.
2026-09-09 23:55:53 -04:00
ethernet
ecf5a6879e fix(pm): publish pin edits without reverting concurrent rows
Pin commands retain snapshots while resolving upstream artifacts.
Merge only the touched rows under the lockfile's advisory lock.
Refuse a changed or deleted row unless it already equals the requested
value. A conflict or invalid file must leave every row unchanged.

Keep the lock file in place so waiting processes share its inode.
Ignore that file in the checkout. Identical retries do not rewrite it.

Verification: two real writers synchronized after reading, stale-row
change/deletion controls, invalid-file preservation and unchanged retry
mtime. The integrated gate passed 85 tests with no failures. Lint passed.
No version pins, installed packages or release drafts were changed.
2026-09-09 23:49:51 -04:00
ethernet
6feb8ac78d fix(pm): use the selected generation for venv freshness
A lock-only stamp reported current without a usable environment.
Use PM's recorded inputs and boot-time selection for own-tree checks.
Do not read or write the foreign-bootstrap stamp for that path.

PM check and sync now share environment validation. Malformed records
remain intact and produce a visible error instead of a healthy result.
Foreign-root bootstrap and bare-import behavior remain unchanged.

Verification: real temporary PM builds, deletion and replacement,
plugin/Python/extra changes, malformed records and transaction neighbors.
The final isolated gate passed 115 tests with no failures. Lint passed.
No publication-lock rewrite or packaged adoption enforcement included.
2026-09-09 23:13:09 -04:00
ethernet
f2db4349e8 fix(pm): inventory features in the staged interpreter
The builder's imported modules could mark an empty dependency tree as
installed. Probe every anchor in one isolated target process. Require
all anchors for a multi-module extra, and process only the selected
tree's .pth files so editable packages retain their launch behavior.

The native builder passes its staged Python explicitly and stops before
manifest publication when the inventory fails. Correct the Hindsight
and Teams anchors using the namespaces in their locked wheels.

Verified: 31 tests passed, 3 host skips. A real target child records its
identity, and a caller mutation back to the builder Python fails the
regression. Both downloaded SDK wheels match uv.lock and pass inventory
and availability checks. Ruff and added-comment checks passed.

No full native package or signing run is claimed.
2026-09-09 21:47:54 -04:00
ethernet
f0667b7048 fix(pm): find conventional bash without a PATH entry
The fallback required a prior PATH hit, so a normal Git for Windows
installation could not satisfy an empty-PATH terminal. Check the
existing conventional locations independently. Keep the staged tool
and usable PATH precedence unchanged.

The native test calls the actual local resolver and executes the
selected shell with an empty PATH and an unusable WindowsApps alias.
The focused shell gate passed. Other hosts retain their existing path.
2026-09-09 20:43:35 -04:00
ethernet
afa48bd413 fix(pm): hash directory links without following them
Directory symlinks contributed no bytes to realized-tree digests.
Windows junctions instead exposed their external target contents.
Hash both as link-target text and remove them from traversal.

Native directory-link and junction tests failed on the base. They now
verify retarget detection, unchanged digests after external writes,
and cycle termination. The existing PM authority and store suites
also pass. No payload facts or live stores were rewritten.
2026-09-09 18:46:37 -04:00
ethernet
f2a19b0e06 refactor: remove unused extraction copies
Use the existing session-export, transcription and DingTalk owners.
Remove unused setup/watchdog helpers and the no-op package migration
hook. No package overrides it; user-state migrations keep their own
existing owners. Keep version-change installation coverage and the
scheduled external plugin compatibility blocks.

Verified with the real adapter, transcription, session-snapshot and
PM core suites. No live messaging service or user-state operation.
2026-09-09 18:21:39 -04:00
ethernet
bb824c381f merge: combine network retries with downloader safety
Keep the retry branch's bounded network policy under installation.
Preserve destination-local atomic publication, partial ownership,
representation checks, aggregate progress and operation-owned pause.
Keep the serial CDN fallback active across subsequent retry attempts.

Retain exact-origin authorization for index reads and safe redirects.
The retry policy does not retry hash, disk or certificate failures.

Verified with canonical native Windows ARM64 tests for both branches,
local-model download consumers, PM core and update resolution.
The commit-build draft remains uncommitted and unchanged.
2026-09-09 17:40:19 -04:00
ethernet
e4cc7f09d9 merge: integrate upstream catalog with PM publication
Keep upstream's reviewed catalog as the only plugin name index.
Catalog pins and custom update sources share staged PM validation.
Publish code and dependencies with recovery after process death.
Reject a concurrent enablement change before publishing disabled code.

Use the manifest loader's supported version in the installer. Keep
probe cooldowns for timeouts, not TLS failures that a CA change fixes.
Preserve the backup, uninstall, browser and memory-provider repairs.

Verified with the canonical runner on native Windows ARM64, real Git
repositories, local TLS endpoints and UV dependency generations.
Desktop catalog tests and both TypeScript checks pass. The full suite
and native release builds were not run. No remote push.
2026-09-09 16:49:27 -04:00
ethernet
9334ae34ec merge: integrate lm-pm downloads with runtime safety repairs
Keep the shared partial lock, representation-bound resume, connection cap,
and atomic destination publication. Add whole-plan progress and caller-owned
pause events without restoring the old mtime-only garbage collector.

Adapt incoming tests to the surviving partial-state owner and accurate HTTP
range responses. Real native CUDA installation paused, resumed, verified the
pinned archives, and executed the installed binary.

The desktop tests, renderer typecheck, lint, PM tests and local-runtime tests
ran against the integrated paths. No package release or channel write ran.
2026-09-09 15:25:56 -04:00
ethernet
bdb52ce711 fix(local-models): use pm pins and resumable component downloads
Use PM for engine binaries, dependent runtime archives, and model files.
Keep one operation-owned pause event through installation and download.
Report whole-plan bytes and retain paused jobs across desktop remounts.

Preserve the completed lm-pm worktree as its own integration parent.
The owning session verified focused Python and desktop tests, actual
Windows ARM64 CUDA downloads, and rendered pause/resume controls.
Combined verification with the safety repairs follows in the merge.
2026-09-09 15:17:09 -04:00
ethernet
1c8fae6180 fix(pm): preserve runtime and user state across failure paths
Keep downloads bound to their remote representation and publish through
atomic destination-local staging. Serialize shared partial ownership.

Keep explicit CA trust scoped to provider probes. Preserve checkpoint
history and edited files, validate all profile inputs before dependency
publication, and separate data removal from installed runtime ownership.

Exclude machine-specific PM state from portable transfers. Keep plugin
files and nested skill tools intact. Preserve native test isolation.

Focused native Windows receipts cover the individual repairs and their
integration. This commit does not claim a full-suite or release build.
2026-09-09 15:17:08 -04:00
ethernet
8e4a804f53 fix(pm): retry transient network failures
A transient HTTP 500 during the range probe aborted bundle staging.
Retry PM-owned requests and body transfers with one bounded policy,
instead of retrying complete installs or stacking caller retry loops.

Keep partial ranges across attempts and interrupt backoff on pause.
Keep the CDN serial fallback active until its source finishes. Do not
retry permanent HTTP errors, certificate failures, bad hashes or disk
errors. Use the same policy for metadata reads and artifact hashing.

Verified: 217 focused tests passed, 1 skipped, and Ruff passed on the
changed Python files. The pinned Windows ARM64 uv archive also survived
an injected HTTP 500 through the PM CLI and its installed binary ran.
The full suite and desktop release build were not run.
2026-09-09 12:54:08 -04:00
ethernet
8b7eae99ef fix(pm): own interpreter selection and dependency recovery
Pin uv and uvx to the PM interpreter instead of ambient Python discovery.
A matching dependency stamp cannot prove that installed files still exist.
Repair now rebuilds the recorded workspace and lock in a fresh generation,
checks startup imports, and publishes the selection only after success.

Run startup recovery before dependency activation. Keep manual PM repair
reachable when the selected environment is damaged. Preserve plugin
selection, retry ownership, and the previous generation on failure.
Remove the separate pip, ensurepip, per-extra, and install-time quarantine
ladders. Keep orphan launcher restoration.

Verification: 717 targeted tests passed on native Windows ARM64, with
56 skipped. Ruff, diff checks, and the source-scoped compat check passed.
A disposable real Hermes install recovered deleted YAML and dotenv files,
then printed CLI help with exit 0. Its lock and stamp stayed unchanged.
The full suite and a release build were not run for this change.
2026-09-08 23:39:55 -04:00
ethernet
712734436e fix(pm): make bootstrap and bundle ownership explicit
Finish bootstrap uv before PM replaces its store entry. Keep failure
receipts stdlib-only and align the cryptography requirement and override
with the locked version.

Let bundle builders declare launch paths and update ownership. Remove
payload discovery, Store probing, and the unused develop command.
Derive Nix Python from the PM lock and share its provenance stamp.

Document setup, activation, optional dependencies, and distribution
ownership. Targeted Windows tests, relocated runtime launches, Electron
bundling, and bilingual docs builds pass. Native Nix and signed-package
acceptance remain CI gates.
2026-09-08 00:24:51 -04:00
ethernet
cd0f97f833 feat(python): pin bundled runtime to 3.14 everywhere (pm, termux lane, CI, installers)
pm python node: 3.14.7+20260901 (freshest python-build-standalone 3.14
build) for the 6 desktop targets; the bionic row moves from the third-party
TUR python3.11 deb to the official termux-main python_3.14.6-1 deb (which
lags PBS by one patch — pinned manually, documented). All 7 digests fetched
from the live sources (PBS release API + termux-main Packages index).
pm/packages.py: main_bin_rel python3.14, deb_package python, bionic fetch
constant, latest_versions guards bionic (no PBS build exists).

termux lane: PYTHON_ABI cp311->cp314, python3.11->python3.14 paths,
libpython3.11.so->3.14, TARGET_ENV 3.11.15->3.14.6 AND sys_platform
linux->android (CPython 3.13+ reports 'android', docs-verified) — linux-
gated markers no longer admit the termux target. runtime_libs.json needs no
change: every python 3.14.6-1 Depends is already staged.

CI: python-version/--python 3.11->3.14 across all 11 workflows incl. the
uv lockfile-check lane. Installers derive the minor from the lock already;
fallbacks bumped. Sandbox images nikolaik/python-nodejs:python3.11-nodejs20
-> python3.14-nodejs22 (tag exists). runtime_repair fall-forward cap now
tracks the <3.15 requires-python window. Docs/README python version claims
updated.
2026-09-07 14:19:18 -04:00
ethernet
cb34ece61d test(plugins): verify sidecar survival and reject masked preservation failures 2026-09-06 22:38:59 -04:00
ethernet
abfefbf1fa test(pm): exercise active profile propagation through real subprocess env 2026-09-06 22:26:02 -04:00
ethernet
7964d77059 feat(pm): assert plugin survival contracts through public admission
- fix conflict classification: uv's real 'Requirements contain
  conflicting URLs' refusal now matches _RESOLVER_MARKERS (was
  misclassified as a generic install error)
- correct stale bisect docstrings: pm has no automatic
  bisect/disable decision; memory-provider preference is a stated
  requirement for any future decision, not implemented behavior
- new tests/pm/test_plugin_survival_contract.py: sidecar without a
  root dependency surface excludes nested/external pyprojects from
  the union; public admit_plugin_set_change refuses an unsatisfiable
  offline local-source union with identity+reason, leaves the
  candidate unenabled/unimported, preserves plugin trees and config,
  records the failure receipt, and the retry commits the resolvable
  candidate; active context home exports to wrapper subprocess env
- document the HERMES_HOME survival contract for memory-provider
  wrappers (mnemosyne-oss/mnemosyne#859)
2026-09-06 22:21:06 -04:00
ethernet
1a09c42414 refactor(bundle): share Python payload assembly across desktop and Termux 2026-09-06 22:21:06 -04:00
ethernet
0b30c2484d merge: integrate termux cli bundles into pm-clean
Merge ethie/cli-bundles at 0765ad689b.
Keep PM runtime publication, install identity, TLS policy, and module
boundaries from pm-clean.

Resolve the Node version-discovery method in its owning class. Preserve
staged tools if a repin download or publication fails. Carry extra-only
memory-provider setup through PM and retain restart-required reporting.
Keep target-specific TUI path assertions and discard obsolete self-lock
fixtures and the orphaned Windows service handler.

Verified locally with the canonical Python runner, root JS checks, TUI
checks/build, shell parsing, and workflow YAML parsing. Existing platform
pins and executable modes are unchanged. No full-suite CI, new bionic
bundle, or phone acceptance is claimed for this merge.
2026-09-06 20:13:45 -04:00
ethernet
fe40130d62 test(termux): keep durable runtime checks and verify the prebuilt TUI
Remove the one-run linkage diagnostic and its expiring artifact dependency. The production wheel import gate and real ELF regression remain. Check the TUI in isolated CI and assert pm exports by behavior, not reloaded function identity.
2026-09-06 16:19:59 -04:00
ethernet
e9dfe6f7d8 fix(pm): retain chained library aliases on no-symlink hosts
Resolve deferred relative file links until no further alias can be materialized. Keep containment checks and leave unresolved cross-package copyright links alone.
2026-09-06 15:49:41 -04:00
ethernet
1ce0998ac9 refactor(termux): reuse package requirements and harden launchers
Use one requirements writer for the wheelhouse and installed venv. Do not retry failed hashes or paused downloads. Skip pure-wheel decompression, preserve runtime library notices, and keep the current working directory off the launcher import path. Document the prerelease canary package without migration or downgrade guidance.
2026-09-06 14:34:52 -04:00
ethernet
af8212b1f6 fix(runtime): route remaining feature consumers through pm
Migrate callers to declared extra names instead of adding legacy alias maps. Preserve lazy-install refusal behavior, isolate reimported test homes, and exercise the real callback boundaries. The relevant integrated batch passes 607 tests.
2026-09-06 14:34:51 -04:00
ethernet
7606b014f5 fix(termux): complete the sealed CLI payload and verify installed startup
Stage npm, ffmpeg and its bionic runtime libraries, and static ARM ripgrep. Bind caches to actual build inputs. Generate entrypoints from the project manifest and verify real CLI/TUI startup and media conversion offline. Keep versions unchanged. Windows service work remains out of scope.
2026-09-06 14:00:42 -04:00
ethernet
eeb3da55da fix(install): restore safe path probes and portable fixtures
Normalize 8.3 paths before stage dispatch and support read-only resolved-path output. Dot-sourcing loads definitions without running the installer. Test the PM delegation contract instead of restoring the removed Node installer.

Use native environment layouts in PM and Hindsight tests. Give steering-test parents no database so child construction cannot create SQLite files at mock paths. Parent verification passed 145 Python tests with one skip and all native PowerShell path, delegation, and stage checks.
2026-09-06 13:42:36 -04:00
ethernet
34d68598ee fix(ci): repair native test selection and bootstrap closure
Use the native path separator for explicit test files. Copy the stdlib runtime owners before Docker invokes PM. Preserve the isolated home in config tests and patch current receipt/backup owners. Remove stale Arabic navigation keys introduced by the merge.
2026-09-06 13:31:25 -04:00
ethernet
acba441012 fix(pm): align context-home publication and recovery scopes
Resolve dependency state and the plugin union from the active home without changing process environment. Use the existing home-root derivation for custom and named profiles. The journal validator checks the same root as the writer.

Verified 71 tests passed, 8 skipped across root resolution, union, recovery, and selection. The new context-only-home regression failed before the fix.
2026-09-06 13:31:24 -04:00
ethernet
3c08d16ba7 fix(pm): close runtime publication and updater audit gaps
Dependency publication now recovers interrupted config/facts changes before
activation and leases live generations during collection. Receipts retain
update correlation and failed steps across nested command boundaries.
Doctor and desktop surfaces report those failures through shared owners.

Move checkout updates out of the desktop facade. Stage a detached Windows
relaunch waiter before shutdown, with bounded handshake and process-birth
checks. Keep packaged lifecycle tests isolated from the installed app.

Native verification exposed two production races: cron maintenance imported
the interactive CLI and rewrote TERMINAL_CWD, and install-ID reads collided
with first publication. Use the existing owners and locks. Plugin checks
now run at startup and each due-gated housekeeping tick, not after 60 ticks.

Share updater-test mutation boundaries and remove collection-root fixtures.
Separate cold MCP startup from command latency and give the real HTTP drip
test enough time to reach body handling.

Root npm check passed, including packaging. The fixed-tree Windows Python
run reported 44557 passed, one failed, and 1404 skipped, plus one retry-only
HTTP test. Those final failures now pass in a 35-test bounded batch. A real
isolated gateway wrote startup and periodic plugin-check receipts.

Full final-tree CI, bundled Sandbox deployment, and actual App Installer
relaunch remain unverified. docs/pm-audit-status.md records these limits.
2026-09-06 11:45:41 -04:00
ethernet
92686159d1 fix(pm): integrate audited runtime and lifecycle repairs
Prepare dependency generations before selecting them. Keep shipped tool
bytes separate from writable additions, and store facts beside their entries.
Validate proposed plugin sets before config publication. Restore the previous
config if the facts write fails.

Consolidate duplicate updater, backup, setup, and voice helpers. Repair
launcher selection, dependency consumers, download ownership, update feeds,
and native Windows process and file handling.

Verification: 206 changed/prior-failing Python files reported 4630 passed,
one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary.
The final targeted rerun reported 234 passed and two skipped. The store
review regression batch reported 83 passed and one skipped. Desktop
TypeScript checks, 56 selected Electron tests, 24 release tests, and the
removed-import/compatibility guards passed.

This is an integration checkpoint, not full audit acceptance. The complete
Python suite has not run on this fixed tree. Crash-atomic plugin publication,
generation cleanup, receipt correlation, and packaged lifecycle acceptance
remain open in docs/pm-audit-status.md.
2026-09-05 22:36:48 -04:00
ethernet
5db2124515 fix(pm): drop x64 vcruntime140_1.dll pre-publish so the recorded digest matches shipped bytes
The win32-arm64 bundle stage deleted vcruntime140_1.dll (the x64 VC
runtime python-build-standalone ships beside ARM64 Python; it cannot
load there and would fail the arch guard) AFTER store.publish() — but
facts.record(digest=tree_digest(entry)) had already hashed the entry
WITH the dll. pm doctor's re-hash then flagged python on every
bundle: 'realized bytes do not match recorded digest' — red from the
commit that introduced both the drop and the digest check (3d12e86ef1),
masked until the smoke test's earlier dead-shim failure was fixed.

The drop belongs in Python.stage(), next to the macOS signing hook —
both are post-extract, pre-publish mutations, so the recorded digest
covers the shipped bytes. The cli-level _drop_unloadable_runtime_files
bundle hook is removed in full (its two tests now pin the stage()
contract). The pm-store CI cache key rotates to v2: failed arm64 runs
saved poisoned facts (dll-less entry + with-dll digest) under the old
key and would restore forever.
2026-09-04 19:01:27 -04:00
ethernet
2d7d43c9be feat(pm): npm ci executor for plugin package.json sidecars — wired
The declared-but-unwired surface from the plugin-deps plan §B item 2:
scan_plugin classified package.json sidecars but nothing executed.

- pm/workspace.install_node_sidecar(): npm ci (with package-lock.json)
  or npm install (without) into the plugin's OWN node_modules — never a
  global prefix; pm's pinned npm store-first (PATH second, the same
  precedence as _uv_binary); lazy-install-gated; returns a reason
  string on failure, never raises. Injectable runner for hermetic
  tests.
- plugins install flow: a package.json plugin gets its own y/n consent
  question; a node-dep failure warns but never blocks the python-dep
  path or the install.

tests: 6 hermetic tests (no-package no-op, ci-vs-install selection by
lockfile presence, lazy-off refusal, failure reason surfacing, runner
explosion isolation). Full sweep: 210 passed, 0 failed.
2026-09-03 14:03:58 -04:00
ethernet
7a7abd3ebe fix(pm): active memory provider joins the union — the mnemosyne path
Memory providers install via memory.provider (mnemosyne's documented
path), not plugins.enabled — enabled_member_dirs never saw them, so a
provider's dep plugin never joined the workspace union. The ordered
enabled read now appends the home's active memory.provider (when its
plugin dir exists on disk; no-dup; rides LAST so the incumbent-wins
tiebreak prefers older plugins over the provider).

Closes the our-side half of the mnemosyne port; the upstream half is
their member manifest. tests: provider joins, ghost-provider skipped,
dual-listed not duplicated. tests/pm: 199 passed, 0 failed.
2026-09-03 13:46:53 -04:00
ethernet
9ba553870f fix(pm): member stamp includes pyproject CONTENT — dep bumps re-sync
Task 4 of the plugin auto-update plan: members_stamp hashed resolved
paths only, so a plugin update that changed its pins left the venv
stamp unchanged — the union never re-synced and the new deps never
installed (path-only hashing made pulled-plugin dep bumps invisible).

members_stamp now folds each member's pyproject.toml bytes into the
hash: same member set + changed pins = changed stamp = re-sync. A
missing/unreadable pyproject degrades to path-only (a vanished
pyproject moves the stamp by dropping its content term — still
correct).

tests: content-change moves stamp, bare-dir hashes on identity,
vanished-pyproject moves stamp without crashing. tests/pm: 196
passed, 0 failed.
2026-09-03 11:56:34 -04:00
ethernet
d5922fb698 fix(pm): derive profile roots from get_default_hermes_root — custom HERMES_HOME joins the union
Profile-discovery gap (found in deployment review): with a custom
HERMES_HOME root (Docker /opt/data, non-default local roots), profiles
live under <HERMES_HOME>/profiles/<name>, but the new pm code scanned
Path.home()/.hermes/profiles in two places (plugins_state._profiles_root,
workspace._plugin_dir_roots). Result: an enabled dep plugin in a
custom-root profile was silently omitted from the union, and bisect
disable decisions never wrote back to that profile's config.

Fix: both sites derive from get_default_hermes_root() — the ONE
authority (hermes_constants), which already handles every layout:
custom HERMES_HOME → that root directly, profile-mode
<root>/profiles/<name> → <root>, standard ~/.hermes unchanged. The
hardcoded restatement (with a comment even citing the HOME-anchor rule
as justification — the authority IS home-anchored, it just also
handles custom roots) is gone.

tests/pm/test_custom_root_union.py: 4 e2e tests through the REAL
authority (no path mocks on the derivation itself): Docker-shape root
(union discovery + disable write-back to the profile's config),
standard layout (unchanged), profile-mode HERMES_HOME (sibling
profiles visible). tests/pm: 194 passed, 0 failed.
2026-09-03 10:55:26 -04:00
ethernet
ef5a98ed57 fix(pm): union sync must install member deps — add --all-packages
Probed live 2026-09-03: plain `uv sync --frozen` installs only the
ROOT project's dependencies — workspace-member deps are locked by
`uv lock` but silently never reach site-packages (a member's
pyfiglet stayed absent under plain --frozen, present under
--all-packages). The union's whole contract is that plugin deps ride
the venv; without the flag every member install is a green-looking
lock over an empty site-packages.

- lock_and_sync now passes --all-packages, with the probe rationale
  in place so the flag can never be 'simplified' away.
- _uv_binary() falls back to shutil.which('uv') when the pm store has
  no provisioned uv — store-first, PATH-second (the activate()
  precedence), fixing 'uv is not installed' lies on dev machines and
  test envs that have uv on PATH but no store.
- tests/pm/test_union_installs_members.py: two REAL end-to-end tests
  (mini workspace, real uv): member deps land in site-packages after
  lock_and_sync, and SURVIVE a second sync (the update-rebuild prune
  contract). These fail under the old flagless command by
  construction — the probe scenario can never silently return.

Found while porting mnemosyne to the union (the manifest-less pip
route prunes on resync; the union route needs member deps to
actually install). tests/pm: 190 passed, 0 failed.
2026-09-03 10:28:23 -04:00
ethernet
ad433f8029 fix(pm): review fixes — enabled-state union, recency tiebreak, gated bridge, sync authority
Spec + standards review (2-subagent /code-review) found five real
gaps against the settled design; all fixed:

- enabled_member_dirs() now FILTERS by enabled state: only plugins in
  some profile's plugins.enabled join the union (a disabled plugin
  never syncs). Result is ENABLE-RECENCY-ORDERED (newest last) via
  new pm/plugins_state.py — order-preserving reads of every profile's
  enabled list — so the bisect's incumbent-wins tiebreak (pop last)
  now disables the most-recently-enabled, not the alphabetical last.
- materialize_legacy_pyproject() is gated on lazy_installs_allowed():
  lazy-off installs keep the plugin dir untouched (materializing
  would create a member candidate and then hard-fail every sealed
  sync). Settled: 'never runs when lazy installs are disabled'.
- plugins_cmd dep install routes through resolve_union + the lazy
  gate (it previously drove lock_and_sync directly, bypassing both):
  the would-be union resolves as a check before enable, with the
  plugin's own bisect decision surfaced as the refusal reason. The
  real sync after enable still runs through sync_venv (the one
  authority, with receipt + write-back).
- resolve_union disable decisions are written back to the plugins
  enabled config (record_disabled_plugins → pm.plugins_state.
  disable_plugins) so hermes plugins list reflects reality and
  re-enable retries; best-effort, never breaks the sync.

tests: member discovery now asserts enabled-filter + recency order +
orphan exclusion; plugins_state suite (5: cross-home reads, order
preservation, disable write-back across homes, noop, garbage-config);
materialize lazy-off test; deps_flow updated to the resolve_union
shape. tests/pm + deps_flow: 193 passed, 0 failed.
2026-09-02 20:21:00 -04:00
ethernet
bf242f3fe7 feat(pm): receipts as the universal machine-readable venv-op surface
Every pm venv sync — startup, plugin install, update rebuild — now
writes a receipt with the SAME schema the updater's receipts use,
into the same <HERMES_HOME>/logs/update_receipts/ dir, separated by a
'kind' field (settled 2026-09-02 plan, task 8):

- pm/receipt.py: begin/record_step/record_venv_rebuild/
  record_bisect/record_feature_list/finalize + rotation (keep 20) +
  latest.json pointer. snapshot() lets the updater EMBED the sync
  sections into its own receipt (one schema, one dir, one reader).
  Exception-swallowing throughout — receipt machinery can never break
  a sync.
- Venv.apply union path: records feature_list, venv_rebuild, and
  bisect decisions; outcome 'ok' | 'bisected' (failures raise before
  the receipt block and surface via the update receipt's error path).
- `hermes pm status` (new verb): prints the latest receipt as JSON —
  the CLI/TUI reader. Desktop IPC reads the same latest.json (the
  hermes:version/syncStatus wiring lands with the desktop branch's
  About/update surface, which already consumes update receipts).

tests/pm/test_receipt.py: 5 tests (roundtrip, latest-pointer,
no-begin no-op, snapshot lifecycle, empty home). tests/pm: 180
passed, 0 failed.
2026-09-02 20:02:15 -04:00
ethernet
ba39525c39 feat(pm): ship the uv cache + mutable-venv bootstrap seed for sealed installs
The blow-the-venv-on-update contract needs rebuilds to be cheap, and
sealed installs need a writable venv at all (settled 2026-09-02 plan,
task 7):

- uv_cache_dir(): hermes-owned machine cache at
  <default hermes root>/cache/uv — content-addressed, shared across
  profiles. uv_env() ALWAYS pins UV_CACHE_DIR there (ambient UV_*
  stripped), so the cache that ships is the cache that gets used.
  First call on a sealed install seeds it from the payload's shipped
  uv-cache/ (read-only payload can't serve uv's working cache); the
  .seeded marker makes it once-only and non-clobbering.
- pm bundle stages the warmed cache into the payload after the venv
  sync (uv-cache/ beside manifest.json) — warm 'uv sync --offline'
  rebuilds probed at 0.4s vs 1.2s cold.
- Venv.venv_dir(): sealed installs resolve the MUTABLE venv to the
  machine hermes root (<root>/venv), not the read-only payload;
  dev/source installs keep the repo-local venv unchanged.
- Venv.seed_mutable_venv(): the bootstrap seed — lazy-off installs
  copy the payload's shipped venv out as the starting point; lazy-on
  installs skip the copy (first sync builds fresh from the shipped
  cache). adopt() triggers it (KeyError-guarded, failure reported
  never fatal — a cold sync still converges).

tests/pm/test_uv_cache.py: 6 tests (env pinning + ambient strip,
payload seed + marker once-only, cold machine, sealed venv_dir, seed
copy idempotence, lazy-on skip). tests/pm: 175 passed, 0 failed.
2026-09-02 20:00:34 -04:00
ethernet
64a6564084 feat(pm): per-extra platform gates — [tool.hermes.extras-platforms]
Platform gating moves from scattered per-package markers to a
readable per-extra authority (settled 2026-09-02 plan, task 9):

- pyproject [tool.hermes.extras-platforms]: extra -> PEP 508 marker.
  matrix = linux-only (python-olm has no win/darwin build);
  google-chat / mem0 = all but win32-arm64 (grpcio has no win_arm64
  wheel). The per-package markers stay — uv's resolver needs them for
  --all-extras (probe: a fully marker-gated extra syncs clean on
  Windows); this table is the readable single authority for WHICH
  extra is supported WHERE.
- pm.extras.extra_supported(): gate consult with an installed-override
  rule (an extra whose anchors import on this machine is supported
  regardless — dev machines, hand-synced venvs) and a fail-open-on-
  malformed-marker posture. Cached table read.
- available() reads gated-off extras as unavailable; ensure_import()
  raises InstallError naming the gate instead of a resolver no-op —
  the adapter degrades with a readable reason, never a mystery.

Live-verified on this win32 host: matrix -> not supported (gate:
sys_platform == 'linux'), web -> supported, table parses.
tests/pm: 169 passed, 0 failed (4 new gate tests).
2026-09-02 19:57:36 -04:00