133 lines
4.3 KiB
Python
133 lines
4.3 KiB
Python
"""Shared isolated database fixture for Hermes execution-safety regressions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest import mock
|
|
|
|
_HERMES_HOME = tempfile.TemporaryDirectory(prefix="hermes-execution-test-home-")
|
|
os.environ["HERMES_HOME"] = _HERMES_HOME.name
|
|
_LANE_SOURCE = Path(
|
|
os.environ.get("HERMES_CLI_LANE_SOURCE", "/tmp/hermes-lane-regression")
|
|
)
|
|
sys.path.insert(0, str(_LANE_SOURCE))
|
|
|
|
from agent import auxiliary_client # noqa: E402
|
|
from cli_lane_config import TerminalIdentity # noqa: E402
|
|
from cli_lane_finalization import _finalize_document_db # noqa: E402
|
|
from hermes_cli import kanban_db # noqa: E402
|
|
from hermes_cli import kanban_decompose # noqa: E402
|
|
|
|
__all__ = ("ExecutionSafetyTestCase", "TerminalIdentity", "_finalize_document_db")
|
|
|
|
|
|
class _FakeCompletions:
|
|
def __init__(self, payload: dict, before_response=None) -> None:
|
|
self.payload = payload
|
|
self.before_response = before_response
|
|
self.calls = 0
|
|
|
|
def create(self, **_kwargs):
|
|
self.calls += 1
|
|
if self.before_response is not None:
|
|
self.before_response()
|
|
return SimpleNamespace(
|
|
choices=[
|
|
SimpleNamespace(
|
|
message=SimpleNamespace(content=json.dumps(self.payload))
|
|
)
|
|
]
|
|
)
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, completions: _FakeCompletions) -> None:
|
|
self.chat = SimpleNamespace(completions=completions)
|
|
|
|
|
|
class ExecutionSafetyTestCase(unittest.TestCase):
|
|
"""Exercise patched upstream APIs against an isolated real database."""
|
|
|
|
def setUp(self) -> None:
|
|
self.connection = kanban_db.connect()
|
|
self.patches = [
|
|
mock.patch.object(
|
|
kanban_decompose,
|
|
"_build_roster",
|
|
return_value=([], {"default"}),
|
|
),
|
|
mock.patch.object(
|
|
kanban_decompose,
|
|
"_load_config",
|
|
return_value={"kanban": {"auto_promote_children": True}},
|
|
),
|
|
mock.patch.object(
|
|
auxiliary_client,
|
|
"get_auxiliary_extra_body",
|
|
return_value={},
|
|
),
|
|
]
|
|
for patch in self.patches:
|
|
patch.start()
|
|
|
|
def tearDown(self) -> None:
|
|
for patch in reversed(self.patches):
|
|
patch.stop()
|
|
self.connection.close()
|
|
|
|
def _client(self, payload: dict, before_response=None) -> _FakeCompletions:
|
|
completions = _FakeCompletions(payload, before_response)
|
|
client = _FakeClient(completions)
|
|
patch = mock.patch.object(
|
|
auxiliary_client,
|
|
"get_text_auxiliary_client",
|
|
return_value=(client, "test-decomposer"),
|
|
)
|
|
patch.start()
|
|
self.addCleanup(patch.stop)
|
|
return completions
|
|
|
|
def _status(self, task_id: str) -> str:
|
|
task = kanban_db.get_task(self.connection, task_id)
|
|
self.assertIsNotNone(task)
|
|
return task.status
|
|
|
|
def _route_executed_task_to_triage(self) -> str:
|
|
task_id = kanban_db.create_task(
|
|
self.connection,
|
|
title="already implemented parent",
|
|
)
|
|
for attempt in range(kanban_db.BLOCK_RECURRENCE_LIMIT):
|
|
if attempt:
|
|
self.assertTrue(kanban_db.unblock_task(self.connection, task_id))
|
|
self.assertIsNotNone(kanban_db.claim_task(self.connection, task_id))
|
|
self.assertTrue(
|
|
kanban_db.block_task(
|
|
self.connection,
|
|
task_id,
|
|
reason="same unavailable capability",
|
|
kind="capability",
|
|
)
|
|
)
|
|
self.assertEqual(self._status(task_id), "triage")
|
|
self.assertEqual(
|
|
len(kanban_db.list_runs(self.connection, task_id)),
|
|
kanban_db.BLOCK_RECURRENCE_LIMIT,
|
|
)
|
|
return task_id
|
|
|
|
def _insert_run(self, task_id: str) -> None:
|
|
with kanban_db.connect_closing() as connection:
|
|
kanban_db._synthesize_ended_run(
|
|
connection,
|
|
task_id,
|
|
outcome="reclaimed",
|
|
summary="concurrent execution evidence",
|
|
)
|