main
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
22f9caf84e |
fix(credentials): scrub the keyed providers schema on rotate/remove
`_scrub_config_yaml_mirrors` reconciles the config.yaml copies of a credential
when it is rotated or removed through the dashboard. It walks `model`,
`auxiliary.<task>`, and `custom_providers.<name>` — but not the keyed
`providers` schema.
`providers` is not a niche section: `get_compatible_custom_providers` documents
it as "the newer keyed schema" (v12+), and it is exactly where the dashboard /
desktop write a custom endpoint's inline key —
`_write_custom_endpoint` sets `providers.<id>.api_key`. That value is a real
credential: the runtime resolver reads it (`runtime_provider` /
`hermes_cli.main` / `model_switch` all read `entry.get("api_key")` off a
`providers` entry), and an inline key outranks the env var.
So the section the scrub skips is the one the dashboard writes to, and both
callers break on it:
- save_provider_env_credential (rotation, #62269): a stale
`providers.<id>.api_key` is left at the OLD value and, being
higher-precedence than the freshly-rotated env var, shadows the rotation —
the "persistent 401 with a key the UI no longer shows" that #62269 fixed,
reintroduced for the newer schema.
- remove_provider_env_credential: its contract is to "remove a credential from
EVERY store it lives in", yet the `providers` copy survives, leaving the
secret in config.yaml after the user asked to delete it.
Walk `providers.<id>` too. The scrub stays value-matched, so an unrelated
endpoint's key is untouched. Only `api_key` is scrubbed here: in the keyed
`providers` schema `api` is the base_url alias, not a credential (unlike
model/auxiliary/custom_providers), so `_fix` takes an explicit field list and
this section passes `("api_key",)` — a provider's endpoint URL is never
rewritten even if it happened to equal the credential string.
tests/hermes_cli/test_credential_lifecycle.py: drive the real PUT/DELETE
/api/env endpoints against a `providers.<id>.api_key` mirror — rotation moves
it to the new key, delete clears it, and a `providers.<id>.api` base_url alias
is preserved. The two scrub tests fail on main (stale key survives); the
base_url guard passes on main as a control. 15 pass here; 345 pass across the
credential-lifecycle + web-server suites (the one failing honcho-merge test
fails identically on clean main).
|
||
|
|
82733a3fdb | fix(credential-pool): materialize pool entry on Desktop PUT /api/env save (#96058) | ||
|
|
39975613b1 |
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker, dashboard, conformance, monitoring, secret_sources, hermes_state, providers). Same rubric as wave 1 (AGENTS.md test policy); security, alternation/caching invariants, issue-number regressions, and E2E kept. Real test-quality fixes found and rooted out along the way: - tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls (DEFAULT_CONFIG smart-approval leaked in) — pinned approval mode=manual via autouse fixture: 17.4s → 0.4s. - test_model_switch_custom_providers.py / test_user_providers_model_switch.py silently probed live provider catalogs (~2s/test) — stubbed cached_provider_model_ids/provider_model_ids/fetch_api_models. - test_telegram_noise_filter.py: 15-platform copy-paste matrix over shared gateway.run logic → 3 representative platforms (55s → 3.9s). - test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on MagicMock agents — interrupt.side_effect now clears _running_agents (22s → 1.0s). - test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait 5s → 0.5s. - test_telegram_init_deadline.py: loop-block margin restored to 1.0s with rationale comment — the watchdog-dump assertion needs the loop blocked well past deadline+grace under parallel load (flaked once in the 40-worker verification run at a 0.2s margin). Verification: full hermetic suite via scripts/run_tests.sh — 2,438 files, 21,718 tests passed, 0 failed, 293.9s wall. Suite totals vs original baseline: 46,820 → 19,757 test functions (−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s. |
||
|
|
6b81590c55 |
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU). |
||
|
|
9a987f142d |
fix(credentials): unified provider key delete/update across .env, auth.json, config.yaml (#67213)
* fix(env): recognize export-prefixed .env lines in save/remove (#40041) load_env() parses bash-compatible 'export KEY=value' lines (#6659), so a hand-added 'export GITHUB_TOKEN=ghp_...' shows as set (green light) in the desktop Tools & Keys page. But save_env_value/remove_env_value only matched plain 'KEY=' lines: - DELETE /api/env 404'd ('not found in .env') — the token could not be removed through the UI - PUT /api/env appended a SECOND line; a later delete removed the new line while the export line silently resurrected the old value Both writers now match assignments through a shared _env_line_defines_key() helper that understands the export prefix. Commented-out lines are still ignored. Regression tests drive the real dashboard endpoint handlers against a temp HERMES_HOME with runtime-constructed classic-PAT-shaped fixtures, covering save-does-not-500, export-line remove, export-line replace-without-duplicate, and the plain-line path staying intact. Fixes #40041 * fix(credentials): unify provider key delete/update across .env, auth.json, config.yaml (#51071 #59761 #62269) A provider API key can live in three stores at once: ~/.hermes/.env, auth.json credential_pool (env-seeded 'env:<VAR>' entries persisted by the pool loader), and config.yaml mirrors (model.api_key, auxiliary.*.api_key, custom_providers[*].api_key). The desktop/dashboard endpoints and the TUI gateway RPCs only ever mutated .env, so the stores diverged: - #51071/#59761: DELETE /api/env removed the key from .env but left the credential_pool entry (the loader is additive-only and never prunes), so the provider kept appearing in the model picker — surviving restart via the stale pool entry + provider_models_cache.json row. - #62269: PUT /api/env rewrote .env but left the OLD key in config.yaml (model.api_key wins over env at client construction), producing 401s with a key the UI no longer showed. New hermes_cli/credential_lifecycle.py is the single choke point: - remove_provider_env_credential(): clears the .env entry, prunes env:<VAR> pool entries across ALL providers (a shared var like GITHUB_TOKEN can seed several), suppresses the env source so a lingering shell export can't re-seed it (matching 'hermes auth remove' semantics), drops the affected providers' model-cache rows, and scrubs value-matched config.yaml api_key mirrors. Returns 'found' spanning every store so a stale pool-only entry is cleanable through the same delete button. - save_provider_env_credential(): writes .env, rotates any config.yaml mirror that held the PREVIOUS value (value-matched — an unrelated inline key is untouched), and lifts a prior env-source suppression so re-adding behaves like 'hermes auth add'. OAuth preservation: only entries with source == 'env:<VAR>' are pruned. OAuth/device-code/manual/borrowed pool entries and providers.<id> OAuth token blocks are never touched by a key-only delete. (model.disconnect in the TUI gateway still clears OAuth via clear_provider_auth — that surface is a full provider disconnect, which is the documented intent there.) Rerouted call sites: PUT/DELETE /api/env (dashboard + desktop), tui_gateway model.save_key / model.disconnect, save_env_value_secure (TUI/gateway secret capture), and hermes config set/unset for env-shaped keys. E2E tests drive the real endpoint handlers against temp-HERMES_HOME fixtures (.env + auth.json + config.yaml with runtime-constructed fake keys) and assert cross-store consistency after delete/update, pool-reload survival ('restart'), OAuth preservation, models-cache invalidation, and the suppress/unsuppress round-trip. Fixes #51071 Fixes #59761 Fixes #62269 |