Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (985) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (986) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
8 changes: 8 additions & 0 deletions MANIFESTO.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,14 @@ EMRG 的架构明确分为**服务端(Server)**与**客户端(Client)**

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

### 第四条附则三 【测试红线·自动升级链不可触发】(最高原则,宿主 2026-08-21 10:35 确立)

**任何时候、任何实例,禁止编写、恢复或以任何形式引入「执行自动升级触发链」的测试用例、脚本或代码路径**——包括真实请求 GitHub releases API、真实读写 `~/.emrg/install/version.txt`、真实创建/写入 `emrg-upgrade` 会话、直接调用 `UpgradeManager.tick()` / daemon `_run_upgrade_session` 而未经完全隔离的测试。实证:长时间运行的 pytest 曾在后台真实执行自动升级逻辑(真实请求 releases API、真实读取 install/version.txt、真实向 `emrg-upgrade` 会话写入降级 prompt),每 5 分钟一次,跨 daemon 重启、甚至在 `emrg stop` 全部进程停止后依然持续写入。违反将导致升级会话污染、真实网络请求与宿主环境干扰、演化机制被后台进程蚕食。

测试必须完全隔离升级链:releases API(httpx AsyncClient)、VERSION_FILE、SESSION_ID(emrg-upgrade)、run_session_cb 全部 mock/打桩;`tests/conftest.py` 设 autouse 兜底守卫,任何触发真实升级链(真实 `UpgradeManager.tick()` / 写 `emrg-upgrade` 会话)的测试直接断言失败。

此为本系统最高原则之一,**永久有效,不适用任何演化机制**,不可被任何程序修改、删除或弱化。执行演化周期时若发现已有测试违反本条,须立即移除并记录。

---

## 第三章:分化与物种形成
Expand Down
65 changes: 65 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,3 +92,68 @@ def _no_real_stop_daemon(*args, **kwargs):
)

monkeypatch.setattr(stop_mod, "stop_daemon", _no_real_stop_daemon)


@pytest.fixture(autouse=True)
def _guard_upgrade_hermeticity(monkeypatch, tmp_path):
"""⛔ Red line (host 2026-08-21T10:35:57): tests must NEVER trigger the
real auto-upgrade chain — real GitHub releases request, real
~/.emrg/install/version.txt read/write, real emrg-upgrade session write.

Empirical evidence: a long-running pytest session (PID 72994, 21h) really
executed the upgrade tick every 5 minutes — real releases API requests,
real install/version.txt reads, real emrg-upgrade session writes with the
downgrade prompt (delay=1440, target=v0.2.57) — continuing across daemon
restarts and even after `emrg stop` stopped all real processes (writes at
10:23:12 / 10:28:15 / 10:33:17 after the 10:22:56 stop).

This autouse fixture blocks every side-effect endpoint of the chain:
1. httpx.AsyncClient in emrg.server.upgrade → AssertionError on
instantiation (module-local: only the upgrade module's reference is
replaced, the global httpx module is untouched). Tests that
legitimately exercise tick() stub it per-test (e.g. test_upgrade.py's
fake client) by patching after this fixture.
2. upgrade.VERSION_FILE → per-test tmp path (the real
~/.emrg/install/version.txt must never be read or written).
3. EmrgServer._get_or_create_session for SESSION_ID ("emrg-upgrade")
→ AssertionError (no real emrg-upgrade session may be created or
written). Tests that exercise the session runner isolate the factory
(monkeypatch.setattr(server, "_get_or_create_session", fake)) after
this fixture, overriding it as usual.
"""
import emrg.server.daemon as daemon_mod
import emrg.server.upgrade as up_mod

# 1. Network — any real GitHub releases request is a loud failure.
class _BlockedHttpx:
class AsyncClient:
def __init__(self, *args, **kwargs):
raise AssertionError(
"test triggered a REAL GitHub releases request through the "
"auto-upgrade chain — ⛔ red-line violation (host "
"2026-08-21T10:35:57); stub emrg.server.upgrade.httpx."
"AsyncClient in your test"
)

monkeypatch.setattr(up_mod, "httpx", _BlockedHttpx)

# 2. Version file — never the real ~/.emrg/install/version.txt.
monkeypatch.setattr(up_mod, "VERSION_FILE", tmp_path / "upgrade-version.txt")

# 3. Upgrade session — creating/writing the real emrg-upgrade session is a
# loud failure; tests that exercise the runner stub the factory after.
_orig_get_or_create = daemon_mod.EmrgServer._get_or_create_session

def _guarded_get_or_create(self, session_id, cwd):
if session_id == up_mod.SESSION_ID:
raise AssertionError(
"test attempted to create the REAL emrg-upgrade session — ⛔ "
"red-line violation (host 2026-08-21T10:35:57); isolate the "
"session factory (monkeypatch.setattr(server, "
"'_get_or_create_session', lambda sid, cwd: <fake>))"
)
return _orig_get_or_create(self, session_id, cwd)

monkeypatch.setattr(
daemon_mod.EmrgServer, "_get_or_create_session", _guarded_get_or_create
)
22 changes: 22 additions & 0 deletions tests/test_upgrade.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -274,6 +274,10 @@ def test_daemon_upgrade_session_runner(monkeypatch, tmp_path):

server = _make_server()
monkeypatch.setattr(server, "_max_tool_rounds", 3)
# ⛔ Red line (host 2026-08-21T10:35:57): tests must never create/write the
# real emrg-upgrade session — isolate the session factory (the conftest
# autouse guard raises on the real one for SESSION_ID).
monkeypatch.setattr(server, "_get_or_create_session", lambda sid, cwd: object())
ran = []

async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
Expand All@@ -293,6 +297,24 @@ async def fake_loop(req, ws, session, cancel_event, allow_tools=True):
assert server._session_busy.get("emrg-upgrade") is False, "busy lock released"


def test_upgrade_chain_hermeticity_guards():
"""⛔ Red line (host 2026-08-21T10:35:57): the conftest autouse guard must
block the real auto-upgrade chain by default — no real GitHub releases
request, no real install/version.txt access. A long-running pytest session
really executed the upgrade chain every 5 minutes (PID 72994, 21h).
"""
from pathlib import Path

import emrg.server.upgrade as up

# 1. Network: the upgrade module's httpx.AsyncClient raises by default.
with pytest.raises(AssertionError, match="red-line"):
up.httpx.AsyncClient()

# 2. Version file: not the real ~/.emrg/install/version.txt.
assert up.VERSION_FILE != Path.home() / ".emrg" / "install" / "version.txt"


# ── no residual references to the removed mechanism ───────────────────────


Expand Down
14 changes: 14 additions & 0 deletions tests/test_ws_e2e.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,19 @@ async def _boot_server(tmp: Path):
sched_mod.config_dir = lambda: tmp # scheduler builds its own projects_file (#738)
connect_mod.config_dir = lambda: tmp

# ⛔ Red line (host 2026-08-21T10:35:57): tests must never run the real
# auto-upgrade chain. serve() unconditionally starts the 5-minute
# _upgrade_tick_loop, which builds UpgradeManager(load_update_config(), …)
# with enabled=True by default — over a long session the tick really
# requested the GitHub releases API and wrote real emrg-upgrade sessions
# (21h pytest incident, PID 72994). Force the manager disabled so the tick
# is a no-op; the conftest autouse guard additionally blocks the network.
import emrg.config as cfg_mod
from emrg.config import UpdateConfig as _UpdateConfig

_orig_load_update_config = cfg_mod.load_update_config
cfg_mod.load_update_config = lambda: _UpdateConfig(enabled=False)

server = daemon_mod.EmrgServer(_make_config())
server.llm = AsyncMock()
server.llm.config = _make_config() # real config so _run_tool_loop reads thresholds
Expand DownExpand Up@@ -130,6 +143,7 @@ async def _cleanup():
connect_mod.config_dir = _orig_connect_cfg
daemon_mod.EMRGD_PORT = _orig_daemon_port
connect_mod.EMRGD_PORT = _orig_connect_port
cfg_mod.load_update_config = _orig_load_update_config

return server, task, _cleanup

Expand Down
Loading