Commit Graph

6 Commits

Author SHA1 Message Date
Teknium
06dc51d62d fix: verification evidence ledger is inert while verify_on_stop is off
The ledger in verification_evidence.db exists only to feed the verify-on-stop
guard, but the recorder kept running on every foreground terminal command and
every file edit after #53552 turned the guard off by default. Users who never
opted in still accumulated a multi-MB database (7 MB / 4.6k rows on one install).

Every ledger entry point (record_terminal_result, record_verify_run,
mark_workspace_edited, verification_status) now checks verify_on_stop_enabled()
first and returns without opening or creating the database when the guard is
off. verification_status reports {"status": "disabled"} in that case; no client
consumes the verification.status RPC yet, so nothing downstream changes.

Existing ledger tests pin HERMES_VERIFY_ON_STOP=1 since they exercise the ledger
itself; the new test proves the off path never creates the file (red on base).
2026-09-09 02:35:41 -07:00
kshitijk4poor
00140a8557 refactor(verify): one honest return type for the compose live-state probe (str | None)
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.
2026-09-06 20:50:02 +05:30
kshitijk4poor
6ccf485f37 fix(verify): refuse compose build/up when the live-container probe cannot answer
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.
2026-09-06 20:50:02 +05:30
ygd58
5b6defc766 fix(verify): refuse compose recipes against a project with running containers
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).
2026-09-06 20:50:02 +05:30
Teknium
fa1a5c0485 Integrate verify subsystem with the existing verification stack
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.
2026-08-07 10:11:05 -07:00
Teknium
47a35d63c0 Port from superagent-ai/grok-cli: verify subsystem (run-recipe detection + environment manifest + hermes verify smoke runner)
Scoped port of grok-cli's verify subsystem:
- agent/verify/recipes.py: static run-recipe detection mirroring grok's
  detection order (Node frameworks w/ lockfile-based package-manager
  choice, Django/FastAPI/Flask/generic Python, Go, Rust, Maven/Gradle,
  Makefile targets, docker-compose)
- agent/verify/environment.py: versioned, user-editable manifest at
  <project>/.hermes/environment.json; tolerant loader; manifest wins
  over fresh detection
- agent/verify/runner.py: bootstrap -> build -> test -> background start
  -> HTTP readiness poll -> process-group teardown, structured result
- hermes verify CLI command (--detect-only, --save, --skip-start,
  --phase, --port, --json)

Sources:
https://github.com/superagent-ai/grok-cli/blob/main/src/verify/recipes.ts
https://github.com/superagent-ai/grok-cli/blob/main/src/verify/environment.ts
2026-08-07 10:11:05 -07:00