Merge upstream 5e645791ac.
Retain the PM feature-flag owner and add upstream connection options.
Use the deny-only window-open policy while trusted external links keep
the existing IPC path. Keep both session-import and external-link copy.
Preserve captured timeout output when adding terminal yield handoff.
Quickstart tests patch the explicit upstream model-assignment owner.
Migrate incoming legacy OS markers to the branch's platforms gate.
Desktop renderer and Electron typechecks passed. Targeted Electron tests
passed (42 tests), Python conflict checks passed (26 tests, 3 skips),
and the plugin-compat import checker passed. CI owns the broad merge gate.
The helper returned container names OR a synthetic pseudo-name so the caller
would refuse; on a hung daemon that rendered as "already has running
container(s) (<docker compose ps timed out ...>)". It now returns the refusal
reason or None, and the caller prints the reason as given.
Review follow-up on the salvaged #103577 guard. Fail-open on a hung or failing
`docker compose ps` reopened the #103567 window: containers may be live and
unobservable, and `docker compose build` would fail against the same daemon
anyway, so refusing loses nothing. Only a missing docker binary falls through.
The guard now also runs only when a mutating phase (build, or start without
skip_start) is selected. Tests collapsed to three contracts: refusal spawns
nothing mutating (running / timed out / failed probe), fall-through (none
running / docker absent), guard skipped for non-mutating phase selections.
Fixes#103567.
`hermes verify`'s compose recipe (`agent/verify/recipes.py::_detect_compose_recipe`)
unconditionally runs `docker compose build` + `docker compose up` against any
directory containing a `docker-compose.yml`, with no check for whether that
directory is already a live deployment. On an image-hash change, `docker
compose up` replaces the running containers -- destroying any state stored
on a container-local path or anonymous volume (not a bind mount).
This caused a real incident: several kanban workers each ran `hermes verify`
as routine self-verification against a workspace that was simultaneously a
live compose deployment; each invocation auto-built and auto-upped the
project; the container carrying a live state DB was replaced (state lost,
recovered from a periodic guard backup after a ~98 minute outage). Nothing
in verify's output indicated it was about to touch live containers.
Implemented the issue's "at minimum" suggested fix: before running any
phase for a `kind == "compose"` recipe, check for running containers via
`docker compose ps --status running` (read-only, never mutates anything).
If any are found, refuse outright with a clear message naming the running
containers, without ever invoking the real `build`/`start` commands as
subprocesses. The refusal surfaces through the exact same PhaseResult/
VerifyResult contract every other phase failure uses (a synthetic failed
"build" phase), so `hermes verify`'s existing JSON/text reporting requires
no special-casing.
The check is best-effort: when `docker compose ps` itself can't run (docker
not installed, or some other environment issue), the function returns None
(not an empty list) and verify proceeds normally rather than blocking on an
unrelated environment gap -- matching how every other recipe kind already
behaves when its own tooling is unavailable. The guard is scoped strictly
to `kind == "compose"`; every other recipe kind (Node, Python, Go, Rust,
Java, Makefile) is completely untouched and never even probes docker.
Verified directly: constructed the exact reported scenario (a compose
Recipe, `docker compose ps` returning running container names via a mocked
subprocess.run) and confirmed run_verify() returns a failed result with a
single synthetic phase, no build/start subprocess ever spawned. Also
verified: no running containers proceeds normally (the real build phase is
attempted); the docker-unavailable case proceeds normally rather than
blocking; and a non-compose recipe never even calls the guard function.
Added 5 regression tests to the existing
tests/verify/test_environment_and_runner.py, following its established
Recipe/run_verify/tmp_path test pattern. Verified as a genuine regression:
reverted just the guard block and confirmed the primary refusal test fails
(the guard's protective behavior is gone) -- a second test that would have
gone on to actually invoke `docker compose build` as a real subprocess
without the guard was interrupted rather than left to run against a
sandbox with no docker daemon, since the first test's failure already
conclusively demonstrates the regression.
24/24 pass in the modified test file; 21/21 pass across two additional
related verify test files (no regression).
Read text files with the encoding utf-8-sig so a BOM at the start of a
file does not cause a Unicode decode error (Windows editors add BOMs).
Reconstructed from ethie/pm commits 48a32b135b + 013219e814 onto the
current upstream/main base: only the utf-8 -> utf-8-sig transforms were
carried (370 exact line pairs across 205 files); pm-rename hunks that
rode in the original commit were left to the pm-store commit, and
utf8sig hunks entangled with content changes ride their owning commit.
Rebuilt on ethie/pm-clean off ac6c8028e0 (upstream/main).
Rescope: hermes verify fills only the runtime-smoke gap and plugs into
the pieces Hermes already has instead of standing beside them.
- agent/verification_evidence.py: record_verify_run() — explicit ledger
write for hermes verify results (shared _insert_evidence factored out
of record_terminal_result). Passing runs mark the workspace passed
like scripts/run_tests.sh; failures are recorded; --phase/--skip-start
runs are recorded as targeted scope.
- hermes_cli/verify_cmd.py: record results into the ledger on completion
(fail-silent, HERMES_SESSION_ID attribution); on the detect path merge
detect_project_facts verify commands the recipe missed into the
recipe's test list (never applied to a saved manifest).
- agent/verification_stop.py: recipe-aware nudge — when the workspace
has a runnable recipe (start command or .hermes/environment.json),
suggest hermes verify --json as the preferred full check; cheap,
try/except-guarded detection that can never break the nudge path.
- agent/verify/recipes.py: document layer ownership (coding_context =
cheap prompt facts; verify/recipes = deep runtime recipe).
- tests/verify/test_ledger_and_nudge_integration.py: 17 tests covering
ledger pass/fail recording, the closed edit->nudge->verify->satisfied
loop, recipe-aware nudge wording + fail-silence, and the facts merge.