diff --git a/gateway/run.py b/gateway/run.py index cabd785aabe6..92d2cbeb4535 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -14344,7 +14344,7 @@ def _save_config_key(key_path: str, value): return t("gateway.reasoning.reset_done") if effort == "none": parsed = {"enabled": False} - elif effort in {"minimal", "low", "medium", "high", "xhigh"}: + elif effort in {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}: parsed = {"enabled": True, "effort": effort} else: return t( diff --git a/tests/gateway/test_multiplex_credential_isolation.py b/tests/gateway/test_multiplex_credential_isolation.py index e177dada69b7..2b4b57c69d3a 100644 --- a/tests/gateway/test_multiplex_credential_isolation.py +++ b/tests/gateway/test_multiplex_credential_isolation.py @@ -761,6 +761,9 @@ class _Pool: def has_credentials(self): return pool_has_creds + def has_available(self): + return pool_has_creds + monkeypatch.setattr( "agent.credential_pool.load_pool", lambda slug: _Pool() ) @@ -852,6 +855,9 @@ class _Pool: def has_credentials(self): return pool_has_creds + def has_available(self): + return pool_has_creds + monkeypatch.setattr("agent.credential_pool.load_pool", lambda slug: _Pool()) providers = list_authenticated_providers(max_models=5) return [p for p in providers if p.get("slug") == "copilot"] diff --git a/tests/hermes_cli/test_models_secret_scope.py b/tests/hermes_cli/test_models_secret_scope.py index ee912850ba5a..4defa0d07726 100644 --- a/tests/hermes_cli/test_models_secret_scope.py +++ b/tests/hermes_cli/test_models_secret_scope.py @@ -23,7 +23,7 @@ def _urlopen(request, timeout): captured["headers"] = dict(request.headers) return _Response() - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr( "agent.anthropic_adapter.resolve_anthropic_token", lambda: "sk-ant-oat01-default-profile", @@ -61,7 +61,7 @@ def _urlopen(*_args, **_kwargs): named_home.mkdir(parents=True) monkeypatch.setenv("HERMES_HOME", str(named_home)) - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr("hermes_cli.auth._load_auth_store", lambda: {}) monkeypatch.setattr( "agent.anthropic_adapter.resolve_anthropic_token", @@ -109,7 +109,7 @@ def _urlopen(request, timeout): default_home.mkdir(parents=True) monkeypatch.setenv("HERMES_HOME", str(default_home)) - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr("hermes_cli.auth._load_auth_store", lambda: {}) monkeypatch.setattr( "agent.anthropic_adapter.resolve_anthropic_token", @@ -162,7 +162,7 @@ def _no_resolve(): default_home.mkdir(parents=True) monkeypatch.setenv("HERMES_HOME", str(default_home)) - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr("hermes_cli.auth._load_auth_store", lambda: {}) monkeypatch.setattr("agent.anthropic_adapter.resolve_anthropic_token", _no_resolve) ss.set_multiplex_active(True) @@ -195,7 +195,7 @@ def _urlopen(request, timeout): captured["headers"] = dict(request.headers) return _Response() - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr( "hermes_cli.auth._load_auth_store", lambda: { @@ -232,7 +232,7 @@ def _urlopen(request, timeout): captured["headers"] = dict(request.headers) return _Response() - monkeypatch.setattr("urllib.request.urlopen", _urlopen) + monkeypatch.setattr(models, "_urlopen_model_catalog_request", _urlopen) monkeypatch.setattr( "hermes_cli.auth._load_auth_store", lambda: { diff --git a/tests/test_hermes_state.py b/tests/test_hermes_state.py index f756532d1353..9079aab78bdf 100644 --- a/tests/test_hermes_state.py +++ b/tests/test_hermes_state.py @@ -745,53 +745,38 @@ def connect_without_trigram(*args, **kwargs): finally: restored.close() - def test_base_fts_rebuilds_when_base_trigger_missing_gate_off( - self, tmp_path, monkeypatch - ): - """Gate-off open must rebuild base FTS when a base trigger is missing. - - Regression: the trigram-disabled open path counted the full six-trigger - set against a threshold of 3, so three surviving trigram triggers could - mask a dropped base trigger (2 base + 3 trigram = 5 >= 3), skipping the - rebuild. Messages written while the base trigger was absent then stayed - unsearchable forever once the trigram table was dropped. Scope the count - to the base triggers so the deficit is seen. - """ + def test_base_fts_rebuilds_when_base_trigger_missing(self, tmp_path): + """Reopening repairs one missing base trigger and reindexes its gap.""" db_path = tmp_path / "state.db" - - # Phase 1: build with trigram ON so the trigram triggers exist on disk. - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: True) seeded = SessionDB(db_path=db_path) try: seeded.create_session(session_id="s1", source="cli") seeded.append_message("s1", role="user", content="already indexed") - assert seeded._trigram_available is True - # Simulate trigger-only degradation from a prior no-FTS5 runtime: - # drop exactly ONE base trigger, leaving the three trigram triggers - # (and the other two base triggers) intact. + assert seeded._conn is not None seeded._conn.execute("DROP TRIGGER IF EXISTS messages_fts_insert") seeded._conn.commit() - # A message written during the missing-base-trigger window is not - # indexed by base FTS. seeded.append_message( "s1", role="assistant", content="gap window base needle" ) finally: seeded.close() - # Phase 2: reopen with the gate flipped OFF. The base rebuild must fire - # despite the surviving trigram triggers, and the trigram table is - # dropped — after which only a rebuilt base index can find the gap msg. - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: False) restored = SessionDB(db_path=db_path) try: - assert restored._fts_enabled is True - assert restored._trigram_available is False - assert restored._fts_table_exists("messages_fts_trigram") is False + assert restored._conn is not None + trigger_names = { + row[0] + for row in restored._conn.execute( + "SELECT name FROM sqlite_master WHERE type = 'trigger' " + "AND name LIKE 'messages_fts_%'" + ) + } + assert "messages_fts_insert" in trigger_names assert len(restored.search_messages("needle")) == 1 finally: restored.close() + def test_is_fts5_unavailable_error_catches_trigram_tokenizer(self): """Unit test: _is_fts5_unavailable_error matches 'no such tokenizer: trigram'.""" fts5_err = sqlite3.OperationalError("no such module: fts5") @@ -915,501 +900,6 @@ def connect_without_trigram(*args, **kwargs): db.close() -class TestExternalContentFtsMigration: - """v20: view-backed third-party-content FTS + trigram config gate + WAL watchdog.""" - - def _seed_inline_v19(self, db_path): - """Create a DB, then rewrite FTS to the pre-v20 INLINE schema at v19. - - Mirrors what an on-disk pre-migration DB looks like: messages_fts / - messages_fts_trigram declared WITHOUT content= (a full text copy per - table) with DELETE-based triggers, and schema_version pinned to 19. - """ - db = SessionDB(db_path=db_path) - db.create_session(session_id="s1", source="cli") - db.append_message( - "s1", role="assistant", content="hello world", - tool_name="web_search", tool_calls='{"q":"kittens"}', - ) - db.append_message("s1", role="user", content="plain text only") - db.close() - - conn = sqlite3.connect(db_path) - conn.executescript( - """ - DROP TRIGGER IF EXISTS messages_fts_insert; - DROP TRIGGER IF EXISTS messages_fts_delete; - DROP TRIGGER IF EXISTS messages_fts_update; - DROP TRIGGER IF EXISTS messages_fts_trigram_insert; - DROP TRIGGER IF EXISTS messages_fts_trigram_delete; - DROP TRIGGER IF EXISTS messages_fts_trigram_update; - DROP TABLE IF EXISTS messages_fts; - DROP TABLE IF EXISTS messages_fts_trigram; - DROP VIEW IF EXISTS messages_search_v; - CREATE VIRTUAL TABLE messages_fts USING fts5(content); - CREATE VIRTUAL TABLE messages_fts_trigram USING fts5(content, tokenize='trigram'); - INSERT INTO messages_fts(rowid, content) - SELECT id, COALESCE(content,'')||' '||COALESCE(tool_name,'')||' '||COALESCE(tool_calls,'') - FROM messages; - INSERT INTO messages_fts_trigram(rowid, content) - SELECT id, COALESCE(content,'')||' '||COALESCE(tool_name,'')||' '||COALESCE(tool_calls,'') - FROM messages; - UPDATE schema_version SET version = 19; - """ - ) - conn.commit() - conn.close() - - def test_migration_switches_to_external_content_and_preserves_search( - self, tmp_path - ): - db_path = tmp_path / "state.db" - self._seed_inline_v19(db_path) - - migrated = SessionDB(db_path=db_path) - try: - # Version advanced and both FTS tables are now external-content - # backed by the messages_search_v view (invariant: content= set). - version = migrated._conn.execute( - "SELECT version FROM schema_version" - ).fetchone()[0] - assert version == SCHEMA_VERSION - fts_sql = migrated._conn.execute( - "SELECT sql FROM sqlite_master WHERE name = 'messages_fts'" - ).fetchone()[0] - assert "content=messages_search_v" in fts_sql.replace("'", "") - assert migrated._conn.execute( - "SELECT count(*) FROM sqlite_master " - "WHERE type='view' AND name='messages_search_v'" - ).fetchone()[0] == 1 - - # v11 intent preserved: tool_name + tool_calls stay searchable. - assert [m["id"] for m in migrated.search_messages("web_search")] - assert [m["id"] for m in migrated.search_messages("kittens")] - # snippet() still emits highlight markers. - hits = migrated.search_messages("hello") - assert hits and ">>>" in hits[0]["snippet"] - - # Incremental maintenance through the new 'delete'-command triggers. - migrated.append_message( - "s1", role="assistant", content="fresh", tool_name="new_tool", - ) - assert [m["id"] for m in migrated.search_messages("new_tool")] - finally: - migrated.close() - - def test_migration_is_idempotent(self, tmp_path): - db_path = tmp_path / "state.db" - self._seed_inline_v19(db_path) - - first = SessionDB(db_path=db_path) - first.close() - # Second open must be a no-op: version stays put and search still works. - second = SessionDB(db_path=db_path) - try: - assert second._conn.execute( - "SELECT version FROM schema_version" - ).fetchone()[0] == SCHEMA_VERSION - assert len(second.search_messages("web_search")) == 1 - finally: - second.close() - - def test_failed_v20_migration_rolls_back_and_stays_v19( - self, tmp_path, monkeypatch - ): - """A v20 rebuild failure must NOT commit the inline-FTS DROPs. - - Regression: the connection is in autocommit mode, so without an - explicit transaction the DROP TABLE statements commit immediately and - a rollback is a no-op — leaving the DB at v19 with the FTS tables gone. - Force the base FTS recreate to fail and assert the pre-migration - schema (inline messages_fts) survives and the version stays at 19. - """ - db_path = tmp_path / "state.db" - self._seed_inline_v19(db_path) - - # Make the external-content recreate fail so fts_migrations_complete - # goes False after the inline tables/triggers were dropped. - real_ensure = SessionDB._ensure_fts_schema - - def _fail_base(self, cursor, table, sql, in_transaction=False): - if table == "messages_fts": - return False - return real_ensure(self, cursor, table, sql, in_transaction) - - monkeypatch.setattr(SessionDB, "_ensure_fts_schema", _fail_base) - - db = SessionDB(db_path=db_path) - try: - # Version must NOT have advanced. - assert db._conn.execute( - "SELECT version FROM schema_version" - ).fetchone()[0] == 19 - # The pre-migration inline FTS table must still exist — the DROPs - # were rolled back, not committed. - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master " - "WHERE type='table' AND name='messages_fts'" - ).fetchone()[0] == 1 - finally: - db.close() - - def test_trigram_gate_off_drops_table_and_falls_back_to_like( - self, tmp_path, monkeypatch - ): - # Gate the trigram index off via the config reader (no config file - # needed — patch the method the constructor consults). - monkeypatch.setattr( - SessionDB, "_read_fts_trigram_config", lambda self: False - ) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - assert db._trigram_available is False - # Trigram table must not exist when gated off. - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 0 - - db.create_session(session_id="s1", source="cli") - db.append_message("s1", role="user", content="大别山项目计划书") - # A 3+ CJK query that would normally use trigram must fall back to - # LIKE without raising, and still find the row. - results = db.search_messages("大别山") - assert len(results) == 1 - assert "大别山" in results[0]["snippet"] - finally: - db.close() - - def test_trigram_off_like_fallback_honors_boolean_operators( - self, tmp_path, monkeypatch - ): - """With trigram gated off, the LIKE fallback must honor AND/NOT. - - Regression: the old fallback OR-joined every non-operator token, so - `A NOT B` returned rows containing B and `A AND B` returned rows - matching only A. Once trigram is disabled this fallback is the only - CJK/substring path, so the boolean structure has to be preserved. - """ - monkeypatch.setattr( - SessionDB, "_read_fts_trigram_config", lambda self: False - ) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="a", source="cli") - db.append_message("a", role="user", content="大别山项目 概述") - db.create_session(session_id="ab", source="cli") - db.append_message("ab", role="user", content="大别山项目 桂林项目 联合") - db.create_session(session_id="ac", source="cli") - db.append_message("ac", role="user", content="大别山项目 武汉分部") - - # NOT excludes the second term. - not_results = db.search_messages("大别山项目 NOT 桂林项目", limit=10) - assert {r["session_id"] for r in not_results} == {"a", "ac"} - - # AND requires both terms. - and_results = db.search_messages("大别山项目 AND 桂林项目", limit=10) - assert {r["session_id"] for r in and_results} == {"ab"} - - # OR still unions. - or_results = db.search_messages("武汉分部 OR 桂林项目", limit=10) - assert {r["session_id"] for r in or_results} == {"ab", "ac"} - - # OR NOT keeps the OR connector (must not collapse to AND NOT): - # "桂林项目 OR NOT 武汉分部" = rows with 桂林项目, OR rows without - # 武汉分部. Session "a" has neither 桂林项目 nor 武汉分部, so the - # NOT arm includes it; "ab" matches the OR arm; "ac" has 武汉分部 - # and not 桂林项目, so it is excluded. - or_not_results = db.search_messages("桂林项目 OR NOT 武汉分部", limit=10) - assert {r["session_id"] for r in or_not_results} == {"a", "ab"} - finally: - db.close() - - def test_drop_trigram_schema_drops_triggers_when_tokenizer_missing( - self, tmp_path, monkeypatch - ): - """When the SQLite build lacks the trigram tokenizer, the DROP TABLE - raises 'no such tokenizer: trigram' and the transactional rollback - undoes the (tokenizer-free) trigger drops. Leaving the triggers behind - makes every later message INSERT fire messages_fts_trigram_insert - against the unusable vtable and crash. The drop must still remove the - triggers so writes degrade to the base FTS/LIKE path. - """ - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: True) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - db.append_message("s1", role="user", content="the quick brown fox") - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 1 - - real_conn = db._conn - - class _NoTokenizerCursor: - def execute(self, sql, *args, **kwargs): - normalized = sql.upper() - if "DROP TABLE" in normalized and "MESSAGES_FTS_TRIGRAM" in normalized: - raise sqlite3.OperationalError("no such tokenizer: trigram") - return real_conn.execute(sql, *args, **kwargs) - - # Tokenizer-missing is a known FTS-unavailable error, so the drop - # reports False (nothing reclaimable) but must NOT leave stale - # triggers pointing at the unusable vtable. - assert db._drop_trigram_schema(_NoTokenizerCursor()) is False - - triggers = { - row[0] - for row in db._conn.execute( - "SELECT name FROM sqlite_master WHERE type='trigger' " - "AND name LIKE 'messages_fts_trigram_%'" - ).fetchall() - } - assert triggers == set() - - # With the triggers gone, a new message INSERT no longer crashes on - # the unusable trigram vtable. - db.append_message("s1", role="user", content="second message after drop") - finally: - db.close() - - def test_drop_trigram_schema_fails_closed_if_tokenizer_fallback_locked( - self, tmp_path, monkeypatch - ): - """Tokenizer-missing recovery must itself fail closed on a lock. - - When DROP TABLE raises 'no such tokenizer: trigram', the fallback - re-drops the triggers in a standalone transaction. If THAT is blocked - (a concurrent gateway/cron holds the write lock), swallowing it would - leave a stale trigger firing against the unusable vtable on the next - INSERT. The lock must propagate so a later uncontended startup retries. - """ - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: True) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - db.append_message("s1", role="user", content="the quick brown fox") - real_conn = db._conn - - class _TokenizerThenLockCursor: - def __init__(self): - self._table_drop_seen = False - - def execute(self, sql, *args, **kwargs): - normalized = sql.upper() - if "DROP TABLE" in normalized and "MESSAGES_FTS_TRIGRAM" in normalized: - self._table_drop_seen = True - raise sqlite3.OperationalError("no such tokenizer: trigram") - # After the tokenizer failure, the standalone trigger-drop - # BEGIN IMMEDIATE is blocked by a concurrent writer. - if self._table_drop_seen and "BEGIN IMMEDIATE" in normalized: - raise sqlite3.OperationalError("database is locked") - return real_conn.execute(sql, *args, **kwargs) - - with pytest.raises(sqlite3.OperationalError, match="database is locked"): - db._drop_trigram_schema(_TokenizerThenLockCursor()) - finally: - db.close() - - def test_drop_trigram_schema_propagates_locked_drop( - self, tmp_path, monkeypatch - ): - """A locked trigram DROP must fail closed, not be swallowed. - - The drop is transactional, so a lock on the table DROP rolls the - trigger drops back too — table + triggers stay together for the next - uncontended startup instead of leaving a stale trigger pointing at a - missing table. - """ - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: True) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - db.append_message("s1", role="user", content="the quick brown fox") - real_conn = db._conn - - class _BlockTableDropCursor: - def execute(self, sql, *args, **kwargs): - normalized = sql.upper() - if "DROP TABLE" in normalized and "MESSAGES_FTS_TRIGRAM" in normalized: - raise sqlite3.OperationalError("database is locked") - return real_conn.execute(sql, *args, **kwargs) - - with pytest.raises(sqlite3.OperationalError, match="database is locked"): - db._drop_trigram_schema(_BlockTableDropCursor()) - - # Failed closed: table and trigram triggers stay together. - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 1 - triggers = { - row[0] - for row in db._conn.execute( - "SELECT name FROM sqlite_master WHERE type='trigger' " - "AND name LIKE 'messages_fts_trigram_%'" - ).fetchall() - } - assert triggers == set(hermes_state._TRIGRAM_FTS_TRIGGERS) - finally: - db.close() - - def test_trigram_gate_on_keeps_table(self, tmp_path, monkeypatch): - monkeypatch.setattr( - SessionDB, "_read_fts_trigram_config", lambda self: True - ) - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - assert db._trigram_available is True - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 1 - finally: - db.close() - - def test_trigram_gate_flip_off_vacuums_and_reclaims_disk( - self, tmp_path, monkeypatch - ): - """Flipping fts_trigram false on an already-built DB must VACUUM. - - Regression: the config-gate open path drops the ~5 GB trigram table but - only moves its pages to the freelist. Without a VACUUM the on-disk file - never shrinks, so the knob's advertised disk recovery wouldn't happen. - Assert the reopen returns freed pages to the OS (file shrinks, freelist - empty), and that a no-drop reopen reports nothing to reclaim (so the - VACUUM is not run on every open). - """ - db_path = tmp_path / "state.db" - - # Phase 1: build a DB with the trigram index populated. - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: True) - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - for i in range(400): - db.append_message( - "s1", role="user", content=f"大别山项目计划书 chunk {i} " * 8 - ) - assert db._trigram_available is True - finally: - db.close() - size_with_trigram = db_path.stat().st_size - - # Phase 2: reopen with the gate flipped OFF — the drop path must VACUUM - # so the freed trigram pages are returned to the OS. - monkeypatch.setattr(SessionDB, "_read_fts_trigram_config", lambda self: False) - db = SessionDB(db_path=db_path) - try: - assert db._trigram_available is False - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 0 - # VACUUM rebuilds the file with an empty freelist. - assert db._conn.execute("PRAGMA freelist_count").fetchone()[0] == 0 - # CJK search still works via the LIKE fallback. - assert len(db.search_messages("大别山")) >= 1 - finally: - db.close() - size_after_drop = db_path.stat().st_size - assert size_after_drop < size_with_trigram, ( - f"trigram-disable did not reclaim disk: " - f"{size_with_trigram} -> {size_after_drop}" - ) - - # Phase 3: a subsequent gate-off reopen has no trigram table to drop, so - # _drop_trigram_schema must report nothing reclaimable (False) — the - # signal that gates the VACUUM off on every steady-state open. - drop_results = [] - real_drop = SessionDB._drop_trigram_schema - - def spy_drop(cursor): - res = real_drop(cursor) - drop_results.append(res) - return res - - monkeypatch.setattr(SessionDB, "_drop_trigram_schema", staticmethod(spy_drop)) - db = SessionDB(db_path=db_path) - try: - assert db._trigram_available is False - finally: - db.close() - assert drop_results == [False], ( - f"gate-off reopen should have nothing to reclaim, got {drop_results}" - ) - - def test_trigram_gate_scoped_to_target_profile(self, tmp_path, monkeypatch): - """A cross-profile open reads the TARGET profile's fts_trigram gate. - - Regression: the launch profile's config must not drive destructive - trigram DDL on a different profile's DB. Launch profile gates trigram - OFF; the opened profile gates it ON — the opened DB must keep trigram. - """ - import yaml - - launch_home = tmp_path / "launch" - launch_home.mkdir() - (launch_home / "config.yaml").write_text( - yaml.safe_dump({"sessions": {"fts_trigram": False}}), encoding="utf-8" - ) - # Active process resolves to the launch profile (trigram OFF). - monkeypatch.setattr(hermes_state, "get_hermes_home", lambda: launch_home) - - # Target profile lives elsewhere and gates trigram ON. - target_home = tmp_path / "other" - target_home.mkdir() - (target_home / "config.yaml").write_text( - yaml.safe_dump({"sessions": {"fts_trigram": True}}), encoding="utf-8" - ) - db = SessionDB(db_path=target_home / "state.db") - try: - # Target profile's ON setting wins — trigram table is kept, not - # dropped by the launch profile's OFF setting. - assert db._fts_trigram_enabled is True - assert db._trigram_available is True - assert db._conn.execute( - "SELECT count(*) FROM sqlite_master WHERE name='messages_fts_trigram'" - ).fetchone()[0] == 1 - finally: - db.close() - - def test_wal_watchdog_shrinks_unpinned_wal(self, tmp_path, monkeypatch): - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - # Suppress the periodic checkpoint so the WAL is free to grow, then - # write enough to push it over a tiny threshold. - monkeypatch.setattr(SessionDB, "_CHECKPOINT_EVERY_N_WRITES", 10 ** 9) - for i in range(400): - db.append_message("s1", role="user", content=("x" * 2000) + str(i)) - - before = db._wal_size_bytes() - assert before > 0 - result = db.wal_watchdog(max_mb=0) - assert result["checked"] is True - assert result["checkpointed"] is True - assert result["pinned"] is False - # Not pinned -> TRUNCATE follows -> file shrinks to ~0. - assert db._wal_size_bytes() < before - finally: - db.close() - - def test_wal_watchdog_noop_below_threshold(self, tmp_path): - db_path = tmp_path / "state.db" - db = SessionDB(db_path=db_path) - try: - db.create_session(session_id="s1", source="cli") - db.append_message("s1", role="user", content="tiny") - # Huge threshold: watchdog must not touch a small WAL. - result = db.wal_watchdog(max_mb=10_000) - assert result["checked"] is False - assert result["checkpointed"] is False - finally: - db.close() # ========================================================================= diff --git a/tools/approval.py b/tools/approval.py index c3aa4992e75c..46c623399358 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -3601,16 +3601,6 @@ def check_execute_code_guard(code: str, env_type: str, # paths don't pay to copy a potentially-large script into this string. command = f"execute_code <<'PY'\n{code}\nPY" - # Redacted copies for user-visible rendering only. An execute_code script - # can embed credentials (e.g. api_key = "sk-..."), and the gateway renders - # this payload directly to Discord/Slack — those messages are - # screenshottable. The raw `command`/`code` are still what get assessed by - # smart approval and executed; redaction is display-only. Approval - # persistence keys off pattern_key, so the allowlist is unaffected. - display_command = _redact_for_approval(command) - display_code = _redact_for_approval(code) - display_description = _redact_for_approval(description) - # Check session/permanent approval — same gate as check_all_command_guards. # Without this, "Approve session" / "Always" choices are stored but never # consulted, so every execute_code call re-prompts the user (#39275).