Commit Graph

206 Commits

Author SHA1 Message Date
ethernet
75a13386fb fix: make macOS shell and mode contracts portable
The macOS runner uses Bash 3.2, which rejects parameter case conversion.
Normalize the GHCR owner with portable tr and verify mixed-case input.

The macOS runner can clear setgid from a directory when chmod applies 2770.
Compare scratch permissions with the native chmod result while preserving all
bits the host accepts.

Tests: scripts/run_tests.sh tests/scripts/test_termux_build_driver.py tests/test_scratch_dir.py
Shell: bash -n scripts/termux/build_builder_image.sh
2026-09-22 00:09:09 -04:00
ethernet
5f1d3294b9 Merge branch 'rev/tests-infra' into ethie/pm-clean 2026-09-21 19:52:58 -04:00
ethernet
843a0095ec Merge branch 'rev/termux' into ethie/pm-clean 2026-09-21 19:52:58 -04:00
ethernet
59624b7ace tests: replace every bare host skipif with platforms()
The OS lanes are marker-driven: list_os_marked_tests.py picks the files
a lane imports from their platforms() specs and the lane selects with
-m platforms. A test gated with skipif(sys.platform != "win32") is
therefore never imported on the Windows lane and skipped everywhere else
— it runs on no host. skipif(sys.platform == "win32") tests were merely
invisible to the lane bookkeeping, but the rule the tree now follows is
one host marker, never a bare skipif.

Mechanical mapping, semantics preserved: skip-on-Windows → "posix",
skip-off-Windows → "windows", skip-off-Linux → "linux", skip-on-macOS →
"not macos". The former skip reasons stay as trailing comments. A
non-host condition (os.geteuid() == 0) stays a separate skipif beside
the marker, spelled getattr(os, "geteuid", ...) so the decorator still
imports on Windows.

Where the conversion would stack two platforms() marks on one test (the
conftest rejects that at collection) the narrower mark wins:
- test_update_wedged_gateway: the class is already platforms("linux");
  its per-test "needs UNIX sockets" marks were redundant and are gone.
- test_process_registry.TestSystemdCgroupIsolation: the class-level
  skip-on-Windows moves onto the 11 methods that had no host mark; the
  11 platforms("linux") methods keep theirs.
- test_file_ops_single_roundtrip: the two fifo tests drop their
  platforms("linux") in favour of the module's "posix" (mkfifo exists on
  macOS; both tests already skip when it does not).
- test_linux_desktop_entry / test_gateway_job_teardown_live: duplicate
  or wider marks removed.
2026-09-21 19:18:15 -04:00
ethernet
4fda664b0f tests: resolve platforms() specs in the lane selector and runner note
scripts/ci/list_os_marked_tests.py matched the literal lane word inside a
platforms() string, so 80 files gated platforms("posix") never reached the
macOS lane ("posix = linux or macOS" was false for CI), and "any" files
reached none. The selector now resolves specs the way the conftest gate
does (posix ⊇ linux+macos, any ⊇ all, "not X" admits the rest) and only
looks inside mark.platforms(...) calls, dropping a false positive whose
only "macos" was inside a generated-file string.

scripts/run_tests_parallel.py still grepped the retired linux_only/
macos_only/windows_only names, so its "N files SKIPPED on this host" note
had been silent since the migration. It now shares the selector's
resolver and names the spec and the lane(s) it runs on.

Restore the platforms("windows") mark that
test_suppress_platform_ver_console_stubs_syscmd_ver lost in the
windows_only migration (its docstring still declared it); it passed
vacuously on Linux and was deselected on the Windows lane.
2026-09-21 18:53:24 -04:00
ethernet
3c53fbdc8b install.ps1: emit the -Json failure frame when Fail ends a stage
Fail ends the script with `exit 1`, which unwinds past the stage
dispatcher's try/catch, so the catch that frames failures as JSON never
ran for the installer's own fatal errors: a `-Stage repository -Json`
run whose clone failed printed the reason via Write-Host only and put
zero frames on stdout (verified on Windows: 0 frames before, 1 after).
Only thrown exceptions were framed.

Fail now emits the failure frame itself when running under -Stage -Json,
so every fatal path yields exactly one frame carrying the original
reason, matching install.sh's EXIT-trap framing.
2026-09-21 18:44:30 -04:00
ethernet
d7466aa3ba release: one strict stable-tag grammar shared by every selector
scripts/releases/semver.STABLE_TAG accepted any-width majors, so
is_valid_version('2026.9.21') was True and docker.require_stable_tag /
stable.py / release.py admitted the legacy CalVer tags that
hermes_cli.source_releases and get_last_tag() already refused. A
workflow_call carrying GitHub's current 'latest' (v2026.9.21) would have
passed the docker publish gate.

hermes_cli.update_channel already owns the canary tag shape; it now owns
STABLE_TAG_RE too (three-digit major cap, no leading zeros, no suffix)
and every stable selector imports it. release.py drops its private
_SEMVER_TAG_RE + CalVer exclusion pair, which the capped major makes
redundant.
2026-09-21 18:42:01 -04:00
ethernet
5e8fbd4919 install.sh: refuse Termux hosts and point at the APT package
check_platform accepted Termux as plain Linux, so `curl | bash` on a
phone walked the source-install ladder: a glibc uv, a lock whose CPython
is the bundled bionic build with no Android wheels, and on-device sdist
builds. The signed APT package is the only supported Termux shape, so
detect Termux the way the runtime does (TERMUX_VERSION or the com.termux
PREFIX) and stop before any stage with `pkg install hermes-agent` and
the setup docs URL. --json surfaces the reason in the stage frame.
2026-09-21 18:41:42 -04:00
ethernet
04bfa58d47 install.sh: refuse to clone over an unrelated destination
The clone publication step did `mv <staged>/tree "$INSTALL_DIR"`. When
INSTALL_DIR already existed as a non-git directory, mv moved the
checkout INSIDE it as INSTALL_DIR/tree and the stage reported success,
leaving later stages to read pm/lock.json from a directory that holds
the user's files instead of a checkout. An existing file made mv fail
with a generic "cannot publish" error.

Before staging the clone, refuse a destination that exists and is not a
Hermes checkout (non-empty dir, file, or symlink) and say what to do; an
empty directory is taken over so the checkout lands AT the path.
2026-09-21 18:40:30 -04:00
ethernet
6a6771e5e7 termux: stamp the deb as a self-contained runtime, not a bundled payload
build_deb.sh wrote HERMES_DESKTOP_VARIANT=bundled, so the Termux stamp
carried payload=bundled and every 'bundled' reader treated the tree as
the repo/ of an Electron payload. hermes uninstall --data then called
resolve_bundle_layout on it and rejected the plan: the deb has no
enclosing app to protect, so data-only removal was impossible on Termux.

Add a 'runtime' variant to write_install_stamp.py for a sealed CLI
runtime with no desktop app around it. It stays sealed for the update
gate and channel identity (source_check, update_channel accept it next
to bundled/light) while is_bundled_payload keeps answering False, so
cleanup planning protects the APT-owned package tree the ordinary way
and never asks where the app is.
2026-09-21 18:37:21 -04:00
ethernet
14b3232cbd fix(test-runner): key the scratch root by user, not a shared literal
The runner's per-run temp roots live under a fixed `/var/tmp/hermes-pytest`, chosen for
real reasons (disk-backed, not hidden, short enough for AF_UNIX sun_path). But a fixed
literal in a world-writable sticky dir belongs to whoever creates it first: a root-owned
root — a container or system-service run — makes every later `makedirs`/`mkdtemp` there
fail with EPERM for every other user on the host, with no way back that does not need
root. That is exactly what happened on luna: /var/tmp/hermes-pytest is root:root 755, so
every local suite run by the login user died at `runner crashed: PermissionError(13)`
before collecting a single test.

Key the name by uid (with the same non-/var/tmp fallback), so no run can be blocked by
another user's leftovers. Invariant test: two uids never share a scratch root, and the
root is created under the expected parent.
2026-09-21 17:24:29 -04:00
ethernet
b265e87678 test: synchronize observable state and reuse prepared PM fixtures 2026-09-21 14:49:05 -04:00
ethernet
d2c60dbdf8 fix(test-runner): preserve PATHEXT for native PowerShell children 2026-09-21 14:03:12 -04:00
ethernet
9b3e84d909 test: align upstream fixtures with PM and isolated service ownership 2026-09-21 13:26:17 -04:00
ethernet
bc2721f17b test: migrate upstream host gates to platforms markers 2026-09-21 13:18:39 -04:00
ethernet
77fad51181 test: pwsh probe reports the launcher's PE subsystem and the base interpreter's exit before the stamp step
Under both pwsh 7.6 and Windows PowerShell 5.1 on the lane `& python.exe ...` returns with
$LASTEXITCODE unset and no side effect while Start-Process sees a real exit code — the shape
PowerShell gives a non-console image. Narrate what the venv launcher is and whether the base
interpreter it points at behaves, so the next red run names the cause.
2026-09-21 11:47:17 -04:00
ethernet
f78f2b14f9 fix: round-1 CI backlog (Windows lane probe, obligation test, generated contract)
- test_source_build_env: the pwsh probe dumps the env through a script file, not
  `-c 'import json, ...'` — pwsh 7.6 on the Windows lane re-quotes native argv and the
  venv launcher receives a truncated -c body (`import` → SyntaxError, no stamp). Drop the
  spawn-path diagnostics that answered that question. On nt prefer Windows PowerShell,
  the shell install-e2e-windows-run.yml actually drives the asset with.
- test_update_fleet_completion: the obligation is host-scoped now; assert through
  `_fleet_restart_obligation_armed()`, not the legacy per-home marker path.
- gateway-contract.generated.ts: regenerated (main's `npm run fix` sweep stripped the
  eslint-disable line the generator emits).
2026-09-21 11:36:13 -04:00
ethernet
7b61a92e28 test: pwsh probe compares native call, Start-Process and cmd.exe for the python child
On the Windows lane `& python ...` under pwsh 7.6.5 returns $?=True with $LASTEXITCODE unset and
no side effect: the child is not started. Exercise the three spawn paths so the next run says which
of them reach the interpreter.
2026-09-21 09:57:52 -04:00
ethernet
00d691ccaa test: pwsh probe reports whether the python child even starts
The Windows lane runs the scriptblock (every narration line prints) but $LASTEXITCODE stays
unset after `& $env:PROBE_PYTHON`: the native call itself produced nothing. Log path existence,
run a bare child first, and print $? and the stamp presence right after the call.
2026-09-21 09:24:12 -04:00
ethernet
744ffcfe36 test: launchd survive-collection execs the osascript child; pwsh probe traps and narrates
- test_source_launcher_publication: launchd ProgramArguments are `/usr/bin/osascript -e` since #71206;
  on Linux exec the child argv the script would spawn (minus the shell redirection tail).
- test_source_build_env: pwsh 7.6.5 on the Windows lane exits 0 without running the child; the probe
  now traps any terminating error (exit 97 + stack) and narrates each step to stderr.
2026-09-21 08:52:49 -04:00
ethernet
61ebb55e7e test: source-build-env probe reports the PowerShell it drove
The Windows lane runs the -File probe to exit 0 with no stamp and no output. Log the shell,
PowerShell version and inputs to stderr, close stdin, and include the return code so the next
red run explains itself.
2026-09-21 08:10:09 -04:00
ethernet
d0c91f47f1 fix: round-10 — PowerShell probe via -File; enable-grant test follows #64228; stage-hold fixture requests access
- test_source_build_env: Windows PowerShell 5.1 runs a multi-line -Command argument only up to
  the first line break and exits 0; the probe is a -File script now (the child never ran, hence
  the empty stamp).
- test_plugins_cmd_enable_disable_nested: `enable` no longer prompts for or writes an undeclared
  allow_tool_override grant (09bcf17801); the test asserts that and uses --no-allow-tool-override
  for the persisted False.
- test_stage_only: the directory hold opens with FILE_LIST_DIRECTORY, not access 0 — a
  zero-access handle does not oppose the rename the repin performs.
2026-09-21 07:31:22 -04:00
ethernet
9ee4397408 fix: round-9 Windows lane — retry argv keeps --force; mint fixture carries venv_sync; stamp probe reports the child
- desktop-update/windows.ps1: the legacy-install retry re-sends the identical request
  (--force included); the contract test compares both attempts.
- test_mint_launchers: the bootstrap imports hermes_cli.venv_sync/steward before
  prepare_launch can return early for a fixture repo; copy them into the tree.
- test_source_build_env: when the pwsh child writes no stamp, fail with the child's
  stdout/stderr instead of a bare FileNotFoundError (the Windows lane hides the cause).
2026-09-21 06:48:59 -04:00
ethernet
f7561667c7 fix: round-8 CI backlog (Windows-only lane)
- tests/conftest.py: `real_bash` fixture — the Windows runners resolve `bash` to System32's
  WSL launcher (UTF-16 "no installed distributions", exit 1); prefer Git for Windows'. Used
  by the setup-pin, install stage-frame and source-launcher shell tests.
- source-build-env.ps1: Test-Path before Remove-Item — under $ErrorActionPreference='Stop'
  a missing identity variable aborted the try block before the child ran (Windows PS 5.1
  raised where pwsh on Unix did not).
- desktop-update/windows.ps1: `--force` precedes the target arguments (the hand-off contract
  test reads argv in that order).
- test_install_ps1_desktop_stage: assert the current contract — the shared completion tail
  (source_completion.py --desktop) builds the products and -IncludeDesktop selects the desktop
  product inside `products` rather than adding a stage. The test predated the completion-tail
  refactor and had been red on the Windows lane since.
- test_windows_native_support: the restart watcher argv is `runtime_command` shaped
  ([python, -I, -c, bootstrap, pid, delay, ...]).
- test_mint_launchers: create the fixture repo's pm/ dir before copying pm/environments.py.
- test_browser_use_pm: console-script launchers report sys.argv[0] without `.exe`.
- test_update_stale_gateway_yield (from main): `_verify_fleet_after_update` has no
  `node_failures` here (PM owns node).
2026-09-21 06:03:05 -04:00
ethernet
1d602a04e3 fix: round-6 CI backlog (32 python, docker arm64, win32-arm64, windows lane, desktop lint)
- pm.environment: the `--no-install-package <name>` root read parses [project].name without
  tomllib on the pre-3.11 bootstrap python (Docker arm64 stage_runtime). Both parsers proven
  to agree on the real pyproject.
- windows-build-deps.ps1 / run_tests.sh: with DISTUTILS_USE_SDK, setuptools takes link.exe
  from PATH; under a bash-hosted step Git for Windows' coreutils `link` shadowed MSVC's.
  The MSVC linker directory now leads PATH (cl.exe was already found — this was the next
  failure in the ruamel-yaml-clib build).
- tests/install/e2e-assets/source-build-env.ps1: clear the identity variables through the
  env: drive — [Environment]::SetEnvironmentVariable(..., 'Process') on .NET/Unix does not
  reach spawned children, so the stamp child still saw GITHUB_SHA. Red→green under nix pwsh.
- old-updater surface: warm_agent_browser_npx_cache is a permanent def in both facade and
  sibling (the frozen surface names both); its compat pointer is retired, and the shims test's
  __module__ check holds.
- tests re-seamed / de-faked: import guard tests use the probe_root fixture (CI has no
  editable finder), the takeover child tree gets a hermes_constants stub, posix.sh hand-off
  test pins HERMES_HOME (our script honours the ambient one), the two Windows-layout
  PYTHONPATH tests are platforms("windows") (they fake `Lib/site-packages` on Linux; pm's
  site_packages() is host-correct), setup-pin test picks Git bash over System32's WSL stub,
  expose_cli's Windows test asserts the installer-convention convergence (the branch retired
  "windows-installer-owned"), plugin-manifest satisfied-dep fixture uses a core dep (pyyaml is
  gone), housekeeping test yaml imports go through hermes_yaml.
- apps/desktop main.ts: three imports restored in round 4 whose users main removed.
2026-09-21 03:44:00 -04:00
ethernet
f3b1399211 fix: round-5 CI backlog after the 779-commit main merge
Merge fallout (my resolution errors, all caught by CI):
- hermes_cli/backup.py + gateway.py: `theirs` on those hunks re-imported clusters HEAD had
  already moved to backup_restore.py / kept in the facade. backup.py loses the 349-line
  duplicate (main's #110179 fix is ported into backup_restore._import_db_member); the
  systemd service-unit cluster returns to gateway.py (PM's _prepare_service_launcher /
  _pm_managed_node_dirs / _systemd_command have no home in main's extraction) with main's
  utf-8-sig read. gateway_service_unit.py is dropped.
- gateway/run.py: main's plugin-update chore is not profile-scoped (the housekeeping
  ordering test pins the scope/drain sequence).
- pyproject + 30 test files: `import yaml` -> `import hermes_yaml as yaml` (pm-clean has no
  pyyaml); gateway/config._bundled_platform_manifest_name reads through hermes_yaml.
- tests re-seamed onto pm-clean's shape: residency admission (installed_engine),
  supervisor child env (binary is a constructor argument), update import guard
  (update_cmd_deps is gone; our probe already scrubs PYTHONPATH — both #115032 invariants
  pass), shallow-count git responses (stash path asks `status --porcelain -z`); dropped
  tests for retired code (_run_node_bootstrap/_ensure_tui_node, Windows resume demotion).
- tests/tools/test_local_env_blocklist.py: restore the two helpers the suite-reduction
  commit dropped and the blocklist import.

Real fixes:
- pm: classify_uv_failure/ResolutionConflict move beside the uv runner (pm.environment,
  stdlib-only). pm.workspace imports tomllib at module level and cannot load on the 3.10
  bootstrap python that streams uv output in the Docker arm64 image.
- tools/browser_tool.warm_agent_browser_npx_cache: back as a permanent definition — it is on
  the frozen old-updater surface, and the revert-scheduled compat pointer does not count.
- hermes_cli/memory_setup: the dashboard's pip row uses pm.environments.
  running_from_selected_environment for installed vs restart_required.
- scripts/windows-build-deps.ps1: export DISTUTILS_USE_SDK/MSSdk so setuptools trusts the
  primed MSVC environment instead of asking vswhere (`env -i` test runner on win32-arm64
  compiling ruamel-yaml-clib); run_tests.sh forwards them.
- tests/pm/test_windows_build_deps.py: start the protocol test from a parent env without the
  toolchain variables the runner job already exports.
- tests/conftest.py scrubs HERMES_BUNDLED_PLUGINS (Nix-wrapped hermes on the dev host);
  tests/home_io_guard.py treats sys.path site-packages under the real home as the
  interpreter's installation (PM-activated developer shell).
- tests-js: four `curly` lint errors from main's new scripts.
2026-09-21 02:47:48 -04:00
ethernet
f130c79b9a ci: bash shebangs in two tests from main; regenerate the google-workspace skill doc 2026-09-21 01:13:59 -04:00
ethernet
9f2ba1b74d merge origin/main (779 commits) into ethie/pm-clean
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).

Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.

uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
2026-09-21 00:58:39 -04:00
teknium1
10c273813d feat(plugin-catalog): screenshots and readme entry fields for the plugin pages
Two optional, submitter-controlled fields on a catalog entry feed the
entry's own page at /docs/plugins/<name>:

- `screenshots:` — up to 6 https URLs on GitHub hosts (same host rule as
  `image`, so the site never fetches from third-party hosts and a raw URL
  pinned to the sha is as immutable as the code).
- `readme: true` — the docs build renders the README from the PINNED
  commit (raw.githubusercontent.com / gitlab.com raw at <sha>), never live
  content, so what a user reads is what the reviewer read.

Validator rejects malformed values (admission), the loader parses and
drops off-host screenshots with a warning (client), and the extractor
emits `screenshots`, `readme`, `readmeUrl` and a `maintainerSlug` for the
author pages. Tests on all three.
2026-09-20 20:43:13 -07:00
liuhao1024
04845f5f3e fix(desktop): skip the local gateway restart on update when the Desktop is remote-served
A Desktop whose active connection is remote (SSH/remote/cloud, including the
registry primary) owns no local messaging gateway, yet the update hand-off
always ran `hermes update --gateway`. On hosts where launchd/service recovery
fails, the updater falls back to a detached local `gateway run --replace`;
with the same Telegram bot token as the remote VPS gateway, the two processes
compete for getUpdates and Telegram rejects one consumer, taking the
production bot offline (#117529).

Pass the ownership down the hand-off: globalRemoteActive() now adds
--no-gateway (posix) / -NoGateway (windows) when the Desktop is remote-served,
and both orchestrators drop --gateway from every update invocation (initial +
retry). The local-ownership default keeps --gateway exactly as before.
2026-09-20 19:30:16 -07:00
fangliquan
6d92f105fe test(install): preserve bash path in probe regression 2026-09-20 15:22:22 -07:00
fangliquan
075158134c test(install): run dependency probe regression on Linux 2026-09-20 15:22:22 -07:00
fangliquan
8c5a5deb79 fix(install): probe the command link directory for dependencies 2026-09-20 15:22:22 -07:00
teknium1
11975e61db test: trim --files-from coverage to two invariants
Keep the two tests that pin the user-facing contract (a file-backed
list bypasses discovery; `-` reads stdin). The mutual-exclusion and
unreadable-path error tests exercised argparse plumbing already covered
by the runner's flag-routing contracts and pushed the suite past the
salvage bar of two invariant tests per fix.
2026-09-20 10:51:40 -07:00
liuhao1024
8828e356f7 fix(tests): let run_tests_parallel take the explicit file list from a file
--files carries the whole list as one argv element, and Linux caps a
single argument at MAX_ARG_STRLEN (128 KiB) - a much smaller limit than
ARG_MAX. The whole-suite list (~210 KB) dies with E2BIG in execve before
the runner's first line runs, so 'run the whole suite except one file'
cannot be expressed through --files at all.

Add --files-from PATH (or '-' for stdin), one path per line, mutually
exclusive with --files. A bare '-' after --files-from is normalized to
the '='-joined form because argparse treats '-' as a positional.
2026-09-20 10:51:40 -07:00
ethernet
f99d780006 test: finish the CI python-tests backlog against a CI-shaped environment
Verified with a build_environment test venv (no editable finder, no committed
PM selection) plus real uv on PATH — the shape CI runs.

- test_old_updater_takeover: the fake NEW checkout carries update_handoff.py;
  the test never relied on the source being importable from an editable venv.
- test_update_launch_completion: record the completion-tail child instead of
  running the checkout's (nonexistent) source_completion.py.
- test_update_missing_configured_deps: source_launch already stubs
  hermes_cli/; link the remaining modules beside the stub.
- test_worker: the injected runtime_lock stub accepts the timeout kwarg the
  worker now passes (it raised TypeError before reaching the lock).
- test_plugin_guard / test_plugin_install_ref: PM publishes plugins only under
  the active home's plugins/ and does so in the worker — install into the
  sandboxed home, and fail the metadata write inside the worker.
- test_pm_build_consumers: setup_toolchain deliberately puts uv on PATH after
  the interpreter (c8bbac5c6e); assert that ordering instead of its absence.
- test_icon_flavors: tile_color reads the most chromatic lower-half pixel (the
  inward contrasting border and LANCZOS smear defeat a fixed coordinate); the
  SHA glyph readback skips cells the portrait covers (da9f6d2dcd renders her in
  front of the badge) and requires a majority of each glyph.
- test_desktop_update_target: the fixture CLI reports the checkout as its
  install directory again (parents[1] inside the string was bumped with the
  file move in cb7c688171).
- test_termux_python_linkage: skip when the host has no shared libpython or no
  patchelf (relocatable python-build-standalone in CI).
- test_source_build_env: smoke-env.mjs is dependency-free (main removed the
  Playwright entry it imported through); pm/_fixtures.stage_host_python copies
  the stdlib beside the interpreter for payload tests.

test_api_server_runs::test_events_stream_forwards_interim_commentary is an
upstream flake (identical file on origin/main fails 2/3 locally).
2026-09-20 12:40:38 -04:00
ethernet
925c08ceca fix: CI python-tests backlog — no import-time dependency syncs, CI-shaped test fixtures
Production:
- agent/bedrock_adapter.py, agent/vertex_adapter.py: pm.ensure_import ran at
  module import. In any process that imports these modules without a committed
  PM selection (CI's build_environment test venv, a fresh checkout) that sync
  rebuilt the dependency environment mid-process and replaced sys.path with a
  generation missing the caller's own packages (anthropic, aiohttp vanished).
  The extra is now ensured at first client build / credential request.
- plugins/platforms/matrix/adapter.py: a complete install needs no
  ensure_and_bind round trip; only a partial one syncs.
- tools/browser_tool.py: drop the facade's duplicate warm_agent_browser_npx_cache
  shim; the compat pointer already resolves to browser_tool_install.

Test harness:
- tests/home_io_guard.py: PATH-entry probes (shutil.which) and the running
  interpreter's own installation (stdlib reads, realpath ancestry, fixture
  symlinks into it) are not Hermes state; a patched Path.expanduser must not
  crash the guard. run_tests.sh no longer filters PATH — the guard owns it.
- tests/tui_gateway/conftest.py: import hermes_bootstrap before any file opens
  a MagicMock hermes_constants window (6 files exited the process at boot).
- tests/hermes_cli/conftest.py probe_root: scratch checkouts the import guard
  probes need hermes_bootstrap.py (the launcher imports it).
- tests/pm/_fixtures.py stage_host_python: a copied relocatable python needs
  its stdlib beside it (No module named 'encodings' on CI).
- tests/install/e2e-assets/smoke-env.mjs: dependency-free env shaping so the
  source-build-env probe runs under bare node (main deleted the Playwright
  entry it was imported through).
- adapt main's new tests to branch seams (model_metadata_http, launch
  completion tail, CI toolchain exports uv after python, source_launch
  hermes_cli stub, systemd_notify single marker).
2026-09-20 11:47:06 -04:00
ethernet
e1576d06a6 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Resolved toward the branch: PM provisions uv/python (main's install.ps1 uv-shim
salvage + its test and workflow steps dropped), the shim re-exec stays retired,
package.json carries no electron-builder block (afterExtract identity stamp wired
into electron-builder.config.cjs instead; after-pack.mjs keeps signing only),
Desktop workspace-deps helpers stay retired. Main's scratch-dir bootstrap
(export_scratch_tmp_env) is taken and re-run after profile resolution.
2026-09-19 22:57:07 -04:00
teknium1
3999096d18 ci: forbid literal /tmp paths outside a burn-down baseline
scripts/check_no_tmp_literals.py flags /tmp path tokens in production code, skills,
docs and prompt strings (tests, CI workflows, Dockerfiles, lockfiles, i18n mirror,
code comments and docstrings exempt; ${TMPDIR:-/tmp} idiom exempt). Opt out one line
with 'no-tmp: ok — <why>' on the line or the line above. _BASELINE lists pre-existing
hits per file: growth fails, burn-down is advisory (--strict-baseline / --print-baseline
to refresh). Wired into lint.yml next to check_compat_pointers.
2026-09-19 10:44:26 -07:00
ethernet
339a6ac7b6 test(install): products and desktop stages share one completion call
dbeebaadfc folded the desktop build into the products stage; the manifest
is one ladder and --include-desktop only adds --desktop to the
source_completion hand-off.
2026-09-19 04:41:58 -04:00
ethernet
06da1225e6 test: pin merged-from-main tests to this branch's contracts
hermes_yaml (ruamel, YAML 1.2) raises its own YAMLError and needs a quoted
URL in flow mappings; the pre-argparse interface probe in main.py is an
allowlisted raw config read; the compat-pointer walker forwards onerror;
tools/lazy_deps.py is a retirement shim with no uv call site; _self stays on
the frozen old-updater surface; the tzdata marker also carries the Python floor.
2026-09-19 04:15:04 -04:00
ethernet
2eec0d9b64 fix(install): the shared completion tail runs from a source slice
- build_update_products builds only the frontends the checkout carries; a
  python-only slice (the installer's acceptance fixture) publishes commands
  and runs maintenance without asking PM for node.
- stderr_timestamp.py is a launcher boot file copied into published
  commands; it inlines the EX_CONFIG code instead of importing gateway.restart.
- Tests: the stamp-writer slice gains hermes_cli/release_channels.py and
  pm/paths.py (the modules update_channel now imports); the source-launch
  fixture records the source_completion hand-off (--finish-update) instead
  of building products; the stdlib recovery probe blocks PM's engine
  modules, not the pm.environments boot leaf; the memory-provider restart
  test selects a generation the running interpreter has not activated;
  the warm-path installer stage is `products`.
2026-09-19 02:53:58 -04:00
ethernet
5e63231e2d fix(icons): the renderer carries its own canary rule; hermes_cli is absent in its venv
The icon generator runs in an isolated icon-build environment (Nix, Docker,
PM) that has no application package, so importing the canonical regex from
hermes_cli.update_channel raised ModuleNotFoundError and broke the nix flake
check, the docker image build and the desktop pack. Keep a local copy and pin
it to the canonical rule with a behavioural test over every tag shape.
2026-09-19 01:38:09 -04:00
ethernet
d70feca03d Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	hermes_cli/update_cmd.py
#	tests/hermes_cli/test_cmd_update.py
2026-09-19 01:14:17 -04:00
teknium1
c07708671d fix(gateway): every adapter session key goes through one seam (+ lint)
A secondary-owned Yuanbao bot keyed its per-group dispatch queue and RecallGuard
entries with the free `build_session_key(source)` — no profile, so `agent:main:` —
while `handle_message` popped under `agent:<owner>:`. Two derivations of one
identity: the group queue was shared across bots and the RecallGuard entries
leaked. Weixin, Telegram's photo batch, Slack's thread key and Raft's wake key
each carried their own copy of the call as well.

Every adapter-side key now comes from `BasePlatformAdapter._source_session_key`
/ `_event_session_key` (owner namespace, runner-seeded isolation flags, and —
after the RoutingIdentity PR — the pinned identity). Weixin's `_text_batch_key`
override is deleted (the base does the same). Slack's thread key reads the
isolation flags from the adapter config the runner seeds, not the store's.

Lint: pattern P32 in `scripts/ci/profile_scope_patterns.json` flags
`build_session_key(` / `SessionSource(` under `gateway/platforms/**` and
`plugins/platforms/**` except `platforms/base.py`; the checker gains an optional
`path_regex` per pattern. Advisory, like every other pattern.

Phase 2 of #88715.
2026-09-18 22:04:43 -07: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
teknium1
0ddba07ad7 fix(ci): report an interpreter crash as CRASHED, not "no tests ran"
When a per-file pytest subprocess dies by signal (the sqlite cross-thread
close in #113186 was a SIGSEGV after every test had passed), faulthandler
prints "Fatal Python error: Segmentation fault" and no summary line, so
every count parses to 0. The runner filed that under "1 file where no
tests ran (collection/import error, ...)" beneath a summary that read
"0 failed" and exited 1 — two wrong diagnoses for one real bug, and it
was misread as a runner problem twice on main.

The runner now detects a signal death or a "Fatal Python error:" banner,
prefixes the captured output with the diagnosis (same convention as the
timeout path), marks the progress line CRASHED, counts "N files CRASHED"
on the summary line, lists the file in its own failure bucket, and no
longer trips the "NO TESTS RAN" guard for a crash that ran tests. The
flake retry already covers crashes (any non-zero rc), so nothing changes
there.
2026-09-18 19:41:51 -07:00
ethernet
cb7c688171 test: mirror the source tree for 27 misfiled root tests; one PM home fixture
Root-level tests/ is for root-level modules; 19 files testing scripts/, 3 testing
pm/ and 5 testing hermes_cli/ move to the mirrored directory (workflow file lists
and cross-imports updated; path arithmetic bumped one level).

tests/pm gains a conftest with the isolated_machine_home fixture that seven
modules had copy-pasted verbatim.
2026-09-18 20:13:26 -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
071ccfbcdf test: restore platforms() host gating that merges reverted to the legacy trio
Four origin/main merges brought back `linux_only` / `macos_only` /
`windows_only` marks in 41 test files, along with the pre-platforms()
versions of scripts/ci/list_os_marked_tests.py and check_os_marker_fakes.py.
Because the legacy names are no longer registered, pytest treated them as
unknown marks — a warning — so every Windows- or macOS-only test RAN on
Linux (test_local_runtime_recovery.py tripped the live-system kill guard).

Rewrite the marks, restore the platforms()-aware CI scripts (keeping main's
os.walk fix for vanishing __pycache__ dirs), drop the stale _BASELINE entries,
and make the conftest reject the retired marks outright so the next merge
cannot resurrect them silently.
2026-09-18 19:06:52 -04:00