chore(release): stamp v0.50.293 — 3-PR batch + 2 Opus follow-ups absorbed
Constituent PRs: #1627 by @franksong2702 — show Hermes Agent version (closes #1606) #1629 by @nesquena-hermes — profile isolation trio (closes #1611, #1612, #1614) #1630 by @Michaelyklam — provider config cleanup regression test (#1597 follow-up) Opus advisor SHIP verdict + 2 SHOULD-FIX absorbed in-release: - load_projects() re-reads from disk inside lock to close migration startup race - _detect_agent_version() uses --dirty for symmetry with _detect_webui_version() 4142 → 4180 tests passing.
This commit is contained in:
1 parent
838645fd50
commit
f3e066b53c
5 files changed
+29
-10
No files matched your search
+12
-5
@@ -14,18 +14,25 @@
|
|||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
4142 → **4181 passing** (+39 regression tests across `tests/test_issue1611_session_profile_filtering.py` (11), `tests/test_issue1612_renamed_root_profile.py` (11), `tests/test_issue1614_project_profile_filtering.py` (11), `tests/test_provider_management.py::test_clean_provider_key_uses_late_bound_config_path` (1), and `tests/test_version_badge.py` agent-detect chain (~5)). 0 regressions. Full suite in ~120s.
|
4142 → **4180 passing** (+38 regression tests across `tests/test_issue1611_session_profile_filtering.py` (11), `tests/test_issue1612_renamed_root_profile.py` (11), `tests/test_issue1614_project_profile_filtering.py` (11), `tests/test_provider_management.py::test_clean_provider_key_uses_late_bound_config_path` (1), and `tests/test_version_badge.py` agent-detect chain (~5)). 0 regressions. Full suite in ~120s.
|
||||||
|
|
||||||
### Pre-release verification
|
### Pre-release verification
|
||||||
|
|
||||||
- Self-built fix (nesquena-hermes), Opus advisor pre-merge pass with 2 SHOULD-FIX absorbed in-PR (see Opus-applied fixes below); independent review APPROVED by nesquena pending.
|
- **Opus advisor on full stage-293 diff: SHIP verdict.** Two SHOULD-FIX items absorbed in-release per <20-LOC defensive policy: (a) `api/models.py:load_projects()` re-reads from disk inside `_PROJECTS_MIGRATION_LOCK` when `_projects_migrated` is found True post-wait — closes a startup-window staleness race where a thread that read pre-migration could return stale untagged rows after a peer migrated and wrote disk; (b) `_detect_agent_version()` now uses `git describe --tags --always --dirty` for symmetry with `_detect_webui_version()`. One non-blocking client-side filter cross-alias edge case deferred as follow-up issue.
|
||||||
|
- Self-built fix (#1629, nesquena-hermes), independent review **APPROVED by nesquena** with comprehensive end-to-end trace, cross-tool verification against fresh agent tarball, security audit, race/state analysis, and 13-row edge-case matrix.
|
||||||
|
- 31 dedicated regression tests for #1611/#1612/#1614 invariants. Source-string assertions pin the active-profile guards on `/api/projects/{rename,delete}` and `/api/session/move`.
|
||||||
- `_is_root_profile` invalidation cycle exercised via test_is_root_profile_invalidation_drops_stale (cache populated, then dropped after simulated profile rename).
|
- `_is_root_profile` invalidation cycle exercised via test_is_root_profile_invalidation_drops_stale (cache populated, then dropped after simulated profile rename).
|
||||||
- `ensure_cron_project` per-profile isolation exercised via test_ensure_cron_project_creates_per_profile (two profiles → two distinct project_ids).
|
- `ensure_cron_project` per-profile isolation exercised via test_ensure_cron_project_creates_per_profile (two profiles → two distinct project_ids).
|
||||||
- Legacy migration covered: untagged projects with sessions inherit session profile; orphan projects fall back to 'default'; idempotent (no-op on second call).
|
|
||||||
- Cross-alias matching pinned: `_profiles_match('default', 'kinni')` returns True only when `kinni` is `is_default`.
|
- Cross-alias matching pinned: `_profiles_match('default', 'kinni')` returns True only when `kinni` is `is_default`.
|
||||||
- Source-string assertions pin the active-profile guards on `/api/projects/{rename,delete}` and `/api/session/move`.
|
|
||||||
|
|
||||||
### Opus-applied fixes (absorbed in-PR per release policy)
|
### Opus-applied fixes (absorbed in-release)
|
||||||
|
|
||||||
|
**From stage-293 review:**
|
||||||
|
|
||||||
|
- **SHOULD-FIX A (project migration startup race)**: `api/models.py:load_projects()` re-reads from disk after acquiring `_PROJECTS_MIGRATION_LOCK` and finding `_projects_migrated=True`. Without this, Thread B that read pre-migration could return stale untagged rows after Thread A migrated and wrote disk — a mutation route on those stale rows could silently overwrite the migration. Window is process-startup-only and very narrow; fix is 8 LOC.
|
||||||
|
- **SHOULD-FIX B (agent version `--dirty` symmetry)**: `_detect_agent_version()` now passes `--dirty` to `git describe --tags --always`, matching `_detect_webui_version()`. Operators with locally-modified agent checkouts now see the dirty marker.
|
||||||
|
|
||||||
|
**Already absorbed in #1629 (in-PR Opus pre-merge pass before staging):**
|
||||||
|
|
||||||
- **SHOULD-FIX #1 (renamed-root client cross-alias)**: removed the strict-equality client filter at `static/sessions.js:1853`. Server-side `_profiles_match` cross-aliases `'default'`-tagged rows to a renamed root `'kinni'`; a strict-equality client filter would have rejected them, dropping every legacy session for renamed-root users. Server is now solely authoritative for profile scoping. Same fix applied to the `otherProfileCount` client fallback.
|
- **SHOULD-FIX #1 (renamed-root client cross-alias)**: removed the strict-equality client filter at `static/sessions.js:1853`. Server-side `_profiles_match` cross-aliases `'default'`-tagged rows to a renamed root `'kinni'`; a strict-equality client filter would have rejected them, dropping every legacy session for renamed-root users. Server is now solely authoritative for profile scoping. Same fix applied to the `otherProfileCount` client fallback.
|
||||||
- **SHOULD-FIX #2 (messaging-source dedupe ordering)**: moved `_keep_latest_messaging_session_per_source(merged)` to AFTER the profile filter at `api/routes.py:2078`. Before: the dedupe ran on the merged-cross-profile list with profile-blind keys, discarding the older profile's row across profiles, then the profile filter scoped to the active profile — leaving zero rows for any messaging identity the active profile shared with another profile. After: filter first, then dedupe within scope.
|
- **SHOULD-FIX #2 (messaging-source dedupe ordering)**: moved `_keep_latest_messaging_session_per_source(merged)` to AFTER the profile filter at `api/routes.py:2078`. Before: the dedupe ran on the merged-cross-profile list with profile-blind keys, discarding the older profile's row across profiles, then the profile filter scoped to the active profile — leaving zero rows for any messaging identity the active profile shared with another profile. After: filter first, then dedupe within scope.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
|
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
|
||||||
>
|
>
|
||||||
> Last updated: v0.50.292 (May 04, 2026) — 4142 tests collected
|
> Last updated: v0.50.293 (May 04, 2026) — 4180 tests collected
|
||||||
> Test source: `pytest tests/ --collect-only -q`
|
> Test source: `pytest tests/ --collect-only -q`
|
||||||
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)
|
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1835,8 +1835,8 @@ Bridged CLI sessions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last updated: v0.50.292, May 04, 2026*
|
*Last updated: v0.50.293, May 04, 2026*
|
||||||
*Total automated tests collected: 4142*
|
*Total automated tests collected: 4180*
|
||||||
*Regression gate: tests/test_regressions.py*
|
*Regression gate: tests/test_regressions.py*
|
||||||
*Run: pytest tests/ -v --timeout=60*
|
*Run: pytest tests/ -v --timeout=60*
|
||||||
*Source: <repo>/*
|
*Source: <repo>/*
|
||||||
+10
-1
@@ -1053,7 +1053,16 @@ def load_projects(*, _migrate: bool = True) -> list:
|
|||||||
with _PROJECTS_MIGRATION_LOCK:
|
with _PROJECTS_MIGRATION_LOCK:
|
||||||
# Re-check inside the lock — another thread may have raced.
|
# Re-check inside the lock — another thread may have raced.
|
||||||
if _projects_migrated:
|
if _projects_migrated:
|
||||||
return projects
|
# Per Opus advisor on stage-293: another thread completed
|
||||||
|
# migration and wrote new state to disk while we waited for
|
||||||
|
# the lock. Our `projects` snapshot is the pre-migration
|
||||||
|
# version; re-read so the caller doesn't see stale untagged
|
||||||
|
# rows (which a mutation route could then write back,
|
||||||
|
# silently overwriting the migration).
|
||||||
|
try:
|
||||||
|
return json.loads(PROJECTS_FILE.read_text(encoding='utf-8'))
|
||||||
|
except Exception:
|
||||||
|
return projects
|
||||||
if _backfill_project_profiles_if_needed(projects):
|
if _backfill_project_profiles_if_needed(projects):
|
||||||
try:
|
try:
|
||||||
save_projects(projects)
|
save_projects(projects)
|
||||||
|
|||||||
+4
-1
@@ -135,7 +135,10 @@ def _detect_agent_version() -> str:
|
|||||||
# file is available (common in source checkouts and developer environments).
|
# file is available (common in source checkouts and developer environments).
|
||||||
if not Path(_AGENT_DIR).exists():
|
if not Path(_AGENT_DIR).exists():
|
||||||
return 'not detected'
|
return 'not detected'
|
||||||
out, ok = _run_git(['describe', '--tags', '--always'], _AGENT_DIR, timeout=3)
|
# Symmetric with _detect_webui_version() above — `--dirty` flags a
|
||||||
|
# locally-modified checkout so operators can see when their agent has
|
||||||
|
# uncommitted changes vs a clean tag. Per Opus advisor on stage-293.
|
||||||
|
out, ok = _run_git(['describe', '--tags', '--always', '--dirty'], _AGENT_DIR, timeout=3)
|
||||||
if ok and out:
|
if ok and out:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user