|
|
|
|
@ -16,9 +16,9 @@ from memory_gateway.backend_adapter_mapping import (
|
|
|
|
|
)
|
|
|
|
|
from memory_gateway.backend_normalization import (
|
|
|
|
|
map_backend_error_to_retryable,
|
|
|
|
|
normalize_evermemos_commit_response,
|
|
|
|
|
normalize_evermemos_ingest_response,
|
|
|
|
|
normalize_evermemos_retrieve_response,
|
|
|
|
|
normalize_everos_commit_response,
|
|
|
|
|
normalize_everos_ingest_response,
|
|
|
|
|
normalize_everos_retrieve_response,
|
|
|
|
|
normalize_openviking_commit_response,
|
|
|
|
|
normalize_openviking_ingest_response,
|
|
|
|
|
normalize_openviking_retrieve_response,
|
|
|
|
|
@ -27,13 +27,14 @@ from memory_gateway.backend_contracts import (
|
|
|
|
|
BackendCommitResult,
|
|
|
|
|
BackendOperation,
|
|
|
|
|
BackendProducedRef,
|
|
|
|
|
BackendRetrieveItem,
|
|
|
|
|
BackendResultStatus,
|
|
|
|
|
BackendRetrieveResult,
|
|
|
|
|
BackendWriteResult,
|
|
|
|
|
OutboxEventStatus,
|
|
|
|
|
)
|
|
|
|
|
from memory_gateway.backend_ref_mapping import map_backend_ref_type
|
|
|
|
|
from memory_gateway.evermemos_client import EverMemOSClient
|
|
|
|
|
from memory_gateway.everos_client import EverOSClient
|
|
|
|
|
from memory_gateway.obsidian_review_client import ObsidianReviewClient
|
|
|
|
|
from memory_gateway.openviking_client import OpenVikingClient
|
|
|
|
|
from memory_gateway.repositories import InMemoryRepository, SQLiteRepository
|
|
|
|
|
@ -51,12 +52,75 @@ from memory_gateway.server_auth import verify_api_key_compat
|
|
|
|
|
from memory_gateway.services_v2 import MemoryGatewayV2Service
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FIXTURE_DIR = Path(__file__).parent / "fixtures" / "backend_responses"
|
|
|
|
|
DOCS_DIR = Path(__file__).parent.parent / "docs"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_backend_fixture(name: str):
|
|
|
|
|
return json.loads((FIXTURE_DIR / name).read_text())
|
|
|
|
|
def backend_response(name: str):
|
|
|
|
|
responses = {
|
|
|
|
|
"openviking_ingest_success.json": {
|
|
|
|
|
"status": "created",
|
|
|
|
|
"id": "ov_turn_fixture_1",
|
|
|
|
|
"uri": "viking://sessions/sess_fixture/turns/ov_turn_fixture_1",
|
|
|
|
|
"metadata": {"schema_version": "openviking.fixture.ingest.v2", "conversation": "SECRET"},
|
|
|
|
|
},
|
|
|
|
|
"openviking_ingest_real_success.json": {
|
|
|
|
|
"status": "created",
|
|
|
|
|
"id": "ov_real_turn_fixture_1",
|
|
|
|
|
"uri": "viking://sessions/ov_real_sess_fixture_1/turns/ov_real_turn_fixture_1",
|
|
|
|
|
"metadata": {"backend_request_id": "ov_req_real_1", "content": "SECRET"},
|
|
|
|
|
},
|
|
|
|
|
"openviking_ingest_real_error_401.json": {"status": "failed", "error": "unauthorized", "error_code": "unauthorized"},
|
|
|
|
|
"openviking_ingest_real_error_422.json": {"status": "failed", "error": "validation failed", "error_code": "validation_error"},
|
|
|
|
|
"openviking_ingest_real_error_500.json": {"status": "failed", "error": "server error", "error_code": "server_error"},
|
|
|
|
|
"openviking_commit_success.json": {
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"session_id": "sess_fixture",
|
|
|
|
|
"result": {
|
|
|
|
|
"refs": [
|
|
|
|
|
{"type": "session_archive", "id": "ov_archive_fixture_1"},
|
|
|
|
|
{"type": "context_resource", "id": "ov_resource_fixture_1"},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"metadata": {"schema_version": "openviking.fixture.commit.v2", "messages": ["SECRET"]},
|
|
|
|
|
},
|
|
|
|
|
"openviking_retrieve_success.json": {
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"result": {
|
|
|
|
|
"items": [
|
|
|
|
|
{"text": "Relevant session summary", "id": "ov_archive_fixture_1", "score": 0.91, "type": "session_archive"},
|
|
|
|
|
{"text": "Relevant resource", "id": "ov_resource_fixture_1", "score": 0.84, "type": "context_resource"},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"metadata": {"schema_version": "openviking.fixture.retrieve.v2", "transcript": "SECRET"},
|
|
|
|
|
},
|
|
|
|
|
"everos_ingest_success.json": {
|
|
|
|
|
"status": "success",
|
|
|
|
|
"memory_id": "em_memory_fixture_1",
|
|
|
|
|
"metadata": {"schema_version": "everos.fixture.ingest.v2", "transcript": "SECRET"},
|
|
|
|
|
},
|
|
|
|
|
"everos_commit_success_multiple_refs.json": {
|
|
|
|
|
"status": "success",
|
|
|
|
|
"data": {
|
|
|
|
|
"produced_refs": [
|
|
|
|
|
{"ref_type": "episodic_memory", "memory_id": "em_episode_fixture_1"},
|
|
|
|
|
{"ref_type": "profile", "profile_id": "em_profile_fixture_1"},
|
|
|
|
|
{"ref_type": "unknown_kind", "id": "em_long_fixture_1"},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"metadata": {"schema_version": "everos.fixture.commit.v2", "messages": ["SECRET"]},
|
|
|
|
|
},
|
|
|
|
|
"everos_retrieve_success.json": {
|
|
|
|
|
"status": "success",
|
|
|
|
|
"data": {
|
|
|
|
|
"items": [
|
|
|
|
|
{"text": "Relevant episodic memory", "memory_id": "em_episode_fixture_1", "score": 0.88, "memory_type": "episodic_memory"},
|
|
|
|
|
{"text": "Relevant profile", "profile_id": "em_profile_fixture_1", "score": 0.73, "memory_type": "profile"},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"metadata": {"schema_version": "everos.fixture.retrieve.v2", "conversation": "SECRET"},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return responses[name]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_ingest_payload(**overrides):
|
|
|
|
|
@ -86,23 +150,53 @@ class FakeOpenVikingClient:
|
|
|
|
|
"native_uri": f"viking://sessions/{payload['session_id']}/{payload['turn_id']}",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async def retrieve_context_v2(self, payload):
|
|
|
|
|
return BackendRetrieveResult(
|
|
|
|
|
backend_type=BackendType.OPENVIKING,
|
|
|
|
|
status=BackendResultStatus.SUCCESS,
|
|
|
|
|
items=[
|
|
|
|
|
BackendRetrieveItem(
|
|
|
|
|
text="OpenViking context for remember",
|
|
|
|
|
source_backend=BackendType.OPENVIKING,
|
|
|
|
|
ref_id="ov_ctx_1",
|
|
|
|
|
score=0.82,
|
|
|
|
|
memory_type="context_resource",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def fake_openviking_factory():
|
|
|
|
|
return FakeOpenVikingClient()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeEverMemOSClient:
|
|
|
|
|
class FakeEverOSClient:
|
|
|
|
|
def ingest_message(self, payload):
|
|
|
|
|
return {
|
|
|
|
|
"status": "success",
|
|
|
|
|
"native_id": f"em_{payload['turn_id']}",
|
|
|
|
|
"native_uri": f"evermemos://memories/{payload['turn_id']}",
|
|
|
|
|
"native_uri": f"everos://memories/{payload['turn_id']}",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def retrieve_context_v2(self, payload):
|
|
|
|
|
return BackendRetrieveResult(
|
|
|
|
|
backend_type=BackendType.EVEROS,
|
|
|
|
|
status=BackendResultStatus.SUCCESS,
|
|
|
|
|
items=[
|
|
|
|
|
BackendRetrieveItem(
|
|
|
|
|
text="EverOS memory for remember",
|
|
|
|
|
source_backend=BackendType.EVEROS,
|
|
|
|
|
ref_id="em_ctx_1",
|
|
|
|
|
score=0.91,
|
|
|
|
|
memory_type="episodic_memory",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class FailingEverMemOSClient:
|
|
|
|
|
|
|
|
|
|
class FailingEverOSClient:
|
|
|
|
|
def ingest_message(self, payload):
|
|
|
|
|
raise RuntimeError("evermemos unavailable")
|
|
|
|
|
raise RuntimeError("everos unavailable")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeCommitOpenVikingClient:
|
|
|
|
|
@ -120,7 +214,7 @@ def fake_commit_openviking_factory(result: BackendCommitResult):
|
|
|
|
|
return factory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeCommitEverMemOSClient:
|
|
|
|
|
class FakeCommitEverOSClient:
|
|
|
|
|
def __init__(self, result: BackendCommitResult) -> None:
|
|
|
|
|
self.result = result
|
|
|
|
|
|
|
|
|
|
@ -149,7 +243,7 @@ def commit_result(
|
|
|
|
|
|
|
|
|
|
def test_v2_adapters_return_backend_write_result_contract():
|
|
|
|
|
ov_result = asyncio.run(
|
|
|
|
|
OpenVikingClient().ingest_conversation_turn(
|
|
|
|
|
OpenVikingClient(mode="offline").ingest_conversation_turn(
|
|
|
|
|
{
|
|
|
|
|
"workspace_id": "ws_1",
|
|
|
|
|
"session_id": "sess_1",
|
|
|
|
|
@ -157,7 +251,7 @@ def test_v2_adapters_return_backend_write_result_contract():
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
em_result = EverMemOSClient().ingest_message(
|
|
|
|
|
em_result = EverOSClient(mode="offline").ingest_message(
|
|
|
|
|
{
|
|
|
|
|
"workspace_id": "ws_1",
|
|
|
|
|
"session_id": "sess_1",
|
|
|
|
|
@ -168,7 +262,7 @@ def test_v2_adapters_return_backend_write_result_contract():
|
|
|
|
|
assert isinstance(ov_result, BackendWriteResult)
|
|
|
|
|
assert isinstance(em_result, BackendWriteResult)
|
|
|
|
|
assert ov_result.backend_type == BackendType.OPENVIKING
|
|
|
|
|
assert em_result.backend_type == BackendType.EVERMEMOS
|
|
|
|
|
assert em_result.backend_type == BackendType.EVEROS
|
|
|
|
|
assert ov_result.operation == BackendOperation.INGEST_TURN
|
|
|
|
|
assert em_result.operation == BackendOperation.INGEST_TURN
|
|
|
|
|
assert ov_result.status == BackendResultStatus.SKIPPED
|
|
|
|
|
@ -180,10 +274,10 @@ def test_backend_env_overrides_enable_real_modes(monkeypatch, tmp_path):
|
|
|
|
|
monkeypatch.setenv("OPENVIKING_BASE_URL", "http://openviking.env.test")
|
|
|
|
|
monkeypatch.setenv("OPENVIKING_API_KEY", "ov-env-token")
|
|
|
|
|
monkeypatch.setenv("OPENVIKING_TIMEOUT_SECONDS", "17")
|
|
|
|
|
monkeypatch.setenv("EVERMEMOS_MODE", "real")
|
|
|
|
|
monkeypatch.setenv("EVERMEMOS_BASE_URL", "http://evermemos.env.test")
|
|
|
|
|
monkeypatch.setenv("EVERMEMOS_API_KEY", "em-env-token")
|
|
|
|
|
monkeypatch.setenv("EVERMEMOS_INGEST_PATH", "/api/v1/memories")
|
|
|
|
|
monkeypatch.setenv("EVEROS_MODE", "real")
|
|
|
|
|
monkeypatch.setenv("EVEROS_BASE_URL", "http://everos.env.test")
|
|
|
|
|
monkeypatch.setenv("EVEROS_API_KEY", "em-env-token")
|
|
|
|
|
monkeypatch.setenv("EVEROS_INGEST_PATH", "/api/v1/memories")
|
|
|
|
|
|
|
|
|
|
config = load_config(str(tmp_path / "missing.yaml"))
|
|
|
|
|
|
|
|
|
|
@ -191,10 +285,10 @@ def test_backend_env_overrides_enable_real_modes(monkeypatch, tmp_path):
|
|
|
|
|
assert config.openviking.url == "http://openviking.env.test"
|
|
|
|
|
assert config.openviking.api_key == "ov-env-token"
|
|
|
|
|
assert config.openviking.timeout == 17
|
|
|
|
|
assert config.evermemos.mode == "real"
|
|
|
|
|
assert config.evermemos.url == "http://evermemos.env.test"
|
|
|
|
|
assert config.evermemos.api_key == "em-env-token"
|
|
|
|
|
assert config.evermemos.ingest_path == "/api/v1/memories"
|
|
|
|
|
assert config.everos.mode == "real"
|
|
|
|
|
assert config.everos.url == "http://everos.env.test"
|
|
|
|
|
assert config.everos.api_key == "em-env-token"
|
|
|
|
|
assert config.everos.ingest_path == "/api/v1/memories"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openviking_default_ingest_does_not_touch_network():
|
|
|
|
|
@ -202,6 +296,7 @@ def test_openviking_default_ingest_does_not_touch_network():
|
|
|
|
|
raise AssertionError("offline OpenViking ingest should not perform HTTP")
|
|
|
|
|
|
|
|
|
|
client = OpenVikingClient(
|
|
|
|
|
mode="offline",
|
|
|
|
|
base_url="http://openviking.test",
|
|
|
|
|
transport=httpx.MockTransport(handler),
|
|
|
|
|
)
|
|
|
|
|
@ -263,7 +358,7 @@ def test_openviking_mode_real_with_base_url_uses_mock_http():
|
|
|
|
|
|
|
|
|
|
def handler(request):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
return httpx.Response(200, json=load_backend_fixture("openviking_ingest_real_success.json"))
|
|
|
|
|
return httpx.Response(200, json=backend_response("openviking_ingest_real_success.json"))
|
|
|
|
|
|
|
|
|
|
client = OpenVikingClient(
|
|
|
|
|
mode="real",
|
|
|
|
|
@ -312,7 +407,7 @@ def test_openviking_real_ingest_mode_real_without_base_url_returns_config_error(
|
|
|
|
|
def test_openviking_real_ingest_success_uses_mock_http_and_normalization():
|
|
|
|
|
seen_payload = {}
|
|
|
|
|
seen_headers = {}
|
|
|
|
|
fixture = load_backend_fixture("openviking_ingest_real_success.json")
|
|
|
|
|
fixture = backend_response("openviking_ingest_real_success.json")
|
|
|
|
|
|
|
|
|
|
def handler(request):
|
|
|
|
|
seen_payload.update(json.loads(request.content.decode()))
|
|
|
|
|
@ -375,7 +470,7 @@ def test_openviking_real_ingest_http_retryable_and_nonretryable_statuses():
|
|
|
|
|
mode="real",
|
|
|
|
|
base_url="http://openviking.test",
|
|
|
|
|
api_key="super-secret-token",
|
|
|
|
|
transport=httpx.MockTransport(lambda request: httpx.Response(status_code, json=load_backend_fixture(name))),
|
|
|
|
|
transport=httpx.MockTransport(lambda request: httpx.Response(status_code, json=backend_response(name))),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result_429 = asyncio.run(client_for_fixture("openviking_ingest_real_error_500.json", 429).ingest_conversation_turn({"session_id": "sess_http"}))
|
|
|
|
|
@ -415,14 +510,14 @@ def test_openviking_real_ingest_invalid_json_returns_failed_retryable():
|
|
|
|
|
assert "SECRET_JSON" not in json.dumps(result.model_dump(mode="json"), ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_default_ingest_does_not_touch_network_even_if_enabled():
|
|
|
|
|
def test_everos_default_ingest_does_not_touch_network_even_if_enabled():
|
|
|
|
|
def handler(request):
|
|
|
|
|
raise AssertionError("EverMemOS ingest should not perform HTTP unless mode=real")
|
|
|
|
|
raise AssertionError("EverOS ingest should not perform HTTP unless mode=real")
|
|
|
|
|
|
|
|
|
|
client = EverMemOSClient(
|
|
|
|
|
client = EverOSClient(
|
|
|
|
|
enabled=True,
|
|
|
|
|
mode="offline",
|
|
|
|
|
base_url="http://evermemos.test",
|
|
|
|
|
base_url="http://everos.test",
|
|
|
|
|
transport=httpx.MockTransport(handler),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -431,8 +526,8 @@ def test_evermemos_default_ingest_does_not_touch_network_even_if_enabled():
|
|
|
|
|
assert result.status == BackendResultStatus.SKIPPED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_real_ingest_mode_real_without_base_url_returns_config_error():
|
|
|
|
|
client = EverMemOSClient(mode="real", base_url="")
|
|
|
|
|
def test_everos_real_ingest_mode_real_without_base_url_returns_config_error():
|
|
|
|
|
client = EverOSClient(mode="real", base_url="")
|
|
|
|
|
|
|
|
|
|
result = client.ingest_message({"session_id": "sess_missing_url", "content": "SECRET"})
|
|
|
|
|
|
|
|
|
|
@ -442,19 +537,19 @@ def test_evermemos_real_ingest_mode_real_without_base_url_returns_config_error()
|
|
|
|
|
assert "SECRET" not in json.dumps(result.model_dump(mode="json"), ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_real_ingest_success_uses_mock_http_and_normalization():
|
|
|
|
|
def test_everos_real_ingest_success_uses_mock_http_and_normalization():
|
|
|
|
|
seen_payload = {}
|
|
|
|
|
seen_headers = {}
|
|
|
|
|
fixture = load_backend_fixture("evermemos_ingest_success.json")
|
|
|
|
|
fixture = backend_response("everos_ingest_success.json")
|
|
|
|
|
|
|
|
|
|
def handler(request):
|
|
|
|
|
seen_payload.update(json.loads(request.content.decode()))
|
|
|
|
|
seen_headers.update(dict(request.headers))
|
|
|
|
|
return httpx.Response(200, json=fixture)
|
|
|
|
|
|
|
|
|
|
client = EverMemOSClient(
|
|
|
|
|
client = EverOSClient(
|
|
|
|
|
mode="real",
|
|
|
|
|
base_url="http://evermemos.test",
|
|
|
|
|
base_url="http://everos.test",
|
|
|
|
|
api_key="em-token",
|
|
|
|
|
transport=httpx.MockTransport(handler),
|
|
|
|
|
)
|
|
|
|
|
@ -472,9 +567,9 @@ def test_evermemos_real_ingest_success_uses_mock_http_and_normalization():
|
|
|
|
|
"metadata": {"channel": "test"},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
expected = normalize_evermemos_ingest_response(fixture)
|
|
|
|
|
expected = normalize_everos_ingest_response(fixture)
|
|
|
|
|
|
|
|
|
|
assert seen_payload["content"] == "SECRET_EM_CONTENT"
|
|
|
|
|
assert seen_payload["messages"][0]["content"] == "SECRET_EM_CONTENT"
|
|
|
|
|
assert seen_headers["x-api-key"] == "em-token"
|
|
|
|
|
assert seen_headers["authorization"] == "Bearer em-token"
|
|
|
|
|
assert result == expected
|
|
|
|
|
@ -484,11 +579,11 @@ def test_evermemos_real_ingest_success_uses_mock_http_and_normalization():
|
|
|
|
|
assert "em-token" not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_real_ingest_errors_are_backend_write_results_and_safe():
|
|
|
|
|
def test_everos_real_ingest_errors_are_backend_write_results_and_safe():
|
|
|
|
|
def client_for_response(status_code, body=None, content=None):
|
|
|
|
|
return EverMemOSClient(
|
|
|
|
|
return EverOSClient(
|
|
|
|
|
mode="real",
|
|
|
|
|
base_url="http://evermemos.test",
|
|
|
|
|
base_url="http://everos.test",
|
|
|
|
|
api_key="em-super-secret-token",
|
|
|
|
|
transport=httpx.MockTransport(lambda request: httpx.Response(status_code, json=body, content=content)),
|
|
|
|
|
)
|
|
|
|
|
@ -517,13 +612,13 @@ def test_evermemos_real_ingest_errors_are_backend_write_results_and_safe():
|
|
|
|
|
assert "em-super-secret-token" not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_real_ingest_timeout_is_retryable_and_safe():
|
|
|
|
|
def test_everos_real_ingest_timeout_is_retryable_and_safe():
|
|
|
|
|
def handler(request):
|
|
|
|
|
raise httpx.ReadTimeout("timeout while sending SECRET_TIMEOUT_CONTENT")
|
|
|
|
|
|
|
|
|
|
client = EverMemOSClient(
|
|
|
|
|
client = EverOSClient(
|
|
|
|
|
mode="real",
|
|
|
|
|
base_url="http://evermemos.test",
|
|
|
|
|
base_url="http://everos.test",
|
|
|
|
|
transport=httpx.MockTransport(handler),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -540,9 +635,9 @@ def test_backend_adapter_mapping_spec_is_contract_first_and_control_plane_only()
|
|
|
|
|
(BackendType.OPENVIKING, BackendOperation.INGEST_TURN),
|
|
|
|
|
(BackendType.OPENVIKING, BackendOperation.COMMIT_SESSION),
|
|
|
|
|
(BackendType.OPENVIKING, BackendOperation.RETRIEVE_CONTEXT),
|
|
|
|
|
(BackendType.EVERMEMOS, BackendOperation.INGEST_TURN),
|
|
|
|
|
(BackendType.EVERMEMOS, BackendOperation.COMMIT_SESSION),
|
|
|
|
|
(BackendType.EVERMEMOS, BackendOperation.RETRIEVE_CONTEXT),
|
|
|
|
|
(BackendType.EVEROS, BackendOperation.INGEST_TURN),
|
|
|
|
|
(BackendType.EVEROS, BackendOperation.COMMIT_SESSION),
|
|
|
|
|
(BackendType.EVEROS, BackendOperation.RETRIEVE_CONTEXT),
|
|
|
|
|
(BackendType.OBSIDIAN, BackendOperation.CREATE_REVIEW_DRAFT),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -551,12 +646,12 @@ def test_backend_adapter_mapping_spec_is_contract_first_and_control_plane_only()
|
|
|
|
|
assert not DISALLOWED_PAYLOAD_FIELDS.intersection(spec.allowed_payload_fields)
|
|
|
|
|
|
|
|
|
|
openviking_commit = get_adapter_mapping_spec(BackendType.OPENVIKING, BackendOperation.COMMIT_SESSION)
|
|
|
|
|
evermemos_ingest = get_adapter_mapping_spec(BackendType.EVERMEMOS, BackendOperation.INGEST_TURN)
|
|
|
|
|
everos_ingest = get_adapter_mapping_spec(BackendType.EVEROS, BackendOperation.INGEST_TURN)
|
|
|
|
|
|
|
|
|
|
assert openviking_commit.adapter_method == "commit_session_v2"
|
|
|
|
|
assert openviking_commit.result_model is BackendCommitResult
|
|
|
|
|
assert evermemos_ingest.adapter_method == "ingest_message"
|
|
|
|
|
assert evermemos_ingest.result_model is BackendWriteResult
|
|
|
|
|
assert everos_ingest.adapter_method == "ingest_message"
|
|
|
|
|
assert everos_ingest.result_model is BackendWriteResult
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_control_plane_persisted_payload_validator_rejects_content_and_raw_request():
|
|
|
|
|
@ -590,26 +685,18 @@ def test_runtime_adapter_request_may_be_transient_but_outbox_payload_is_control_
|
|
|
|
|
validate_control_plane_persisted_payload(outbox_payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_commit_and_retrieve_adapter_skeletons_return_unified_contracts():
|
|
|
|
|
def test_commit_adapter_skeletons_return_unified_contracts():
|
|
|
|
|
payload = {"workspace_id": "ws_1", "session_id": "sess_1", "gateway_id": "gw_1"}
|
|
|
|
|
|
|
|
|
|
ov_commit = asyncio.run(OpenVikingClient().commit_session_v2(payload))
|
|
|
|
|
ov_retrieve = asyncio.run(OpenVikingClient().retrieve_context_v2(payload))
|
|
|
|
|
em_commit = EverMemOSClient().extract_profile_long_term_v2(payload)
|
|
|
|
|
em_retrieve = EverMemOSClient().retrieve_context_v2(payload)
|
|
|
|
|
ov_commit = asyncio.run(OpenVikingClient(mode="skeleton").commit_session_v2(payload))
|
|
|
|
|
em_commit = EverOSClient(mode="skeleton").extract_profile_long_term_v2(payload)
|
|
|
|
|
|
|
|
|
|
assert isinstance(ov_commit, BackendCommitResult)
|
|
|
|
|
assert isinstance(em_commit, BackendCommitResult)
|
|
|
|
|
assert isinstance(ov_retrieve, BackendRetrieveResult)
|
|
|
|
|
assert isinstance(em_retrieve, BackendRetrieveResult)
|
|
|
|
|
assert ov_commit.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert em_commit.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert ov_retrieve.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert em_retrieve.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert ov_commit.refs[0].ref_type == MemoryRefType.SESSION_ARCHIVE
|
|
|
|
|
assert {ref.ref_type for ref in em_commit.refs} == {MemoryRefType.PROFILE, MemoryRefType.LONG_TERM_MEMORY}
|
|
|
|
|
assert len(ov_retrieve.items) == 1
|
|
|
|
|
assert len(em_retrieve.items) == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_client_skeletons_use_normalization_contracts_and_safe_metadata():
|
|
|
|
|
@ -621,8 +708,8 @@ def test_client_skeletons_use_normalization_contracts_and_safe_metadata():
|
|
|
|
|
"content": "TRANSIENT_CONTENT_ONLY",
|
|
|
|
|
"raw_request": {"content": "TRANSIENT_CONTENT_ONLY"},
|
|
|
|
|
}
|
|
|
|
|
ov_client = OpenVikingClient()
|
|
|
|
|
em_client = EverMemOSClient()
|
|
|
|
|
ov_client = OpenVikingClient(mode="skeleton")
|
|
|
|
|
em_client = EverOSClient(mode="skeleton")
|
|
|
|
|
|
|
|
|
|
ov_ingest = asyncio.run(ov_client.ingest_conversation_turn(payload))
|
|
|
|
|
ov_commit = asyncio.run(ov_client.commit_session_v2(payload))
|
|
|
|
|
@ -649,8 +736,8 @@ def test_client_skeletons_use_normalization_contracts_and_safe_metadata():
|
|
|
|
|
"status": "skipped",
|
|
|
|
|
"memory_id": "turn_contract",
|
|
|
|
|
"metadata": {
|
|
|
|
|
"reason": "evermemos_v2_ingest_adapter_not_configured",
|
|
|
|
|
"schema_version": "evermemos.fixture.ingest.v2",
|
|
|
|
|
"reason": "everos_v2_ingest_adapter_not_configured",
|
|
|
|
|
"schema_version": "everos.fixture.ingest.v2",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
@ -667,57 +754,30 @@ def test_client_skeletons_use_normalization_contracts_and_safe_metadata():
|
|
|
|
|
assert blocked not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_retrieve_skeletons_use_retrieve_normalization_and_safe_metadata():
|
|
|
|
|
payload = {
|
|
|
|
|
"workspace_id": "ws_1",
|
|
|
|
|
"user_id": "user_a",
|
|
|
|
|
"session_id": "sess_retrieve_contract",
|
|
|
|
|
"query": "fixture query",
|
|
|
|
|
"content": "TRANSIENT_RETRIEVE_CONTENT",
|
|
|
|
|
}
|
|
|
|
|
ov_result = asyncio.run(OpenVikingClient().retrieve_context_v2(payload))
|
|
|
|
|
em_result = EverMemOSClient().retrieve_context_v2(payload)
|
|
|
|
|
|
|
|
|
|
assert isinstance(ov_result, BackendRetrieveResult)
|
|
|
|
|
assert isinstance(em_result, BackendRetrieveResult)
|
|
|
|
|
assert ov_result.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert em_result.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert ov_result.items[0].source_backend == BackendType.OPENVIKING
|
|
|
|
|
assert em_result.items[0].source_backend == BackendType.EVERMEMOS
|
|
|
|
|
assert ov_result.items[0].text
|
|
|
|
|
assert em_result.items[0].ref_id
|
|
|
|
|
serialized = json.dumps(
|
|
|
|
|
{"ov": ov_result.model_dump(mode="json"), "em": em_result.model_dump(mode="json")},
|
|
|
|
|
ensure_ascii=False,
|
|
|
|
|
)
|
|
|
|
|
for blocked in ("TRANSIENT_RETRIEVE_CONTENT", "content", "raw_request", "messages", "conversation", "transcript"):
|
|
|
|
|
assert blocked not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openviking_commit_skeleton_ref_type_is_mapped_from_fixture():
|
|
|
|
|
result = asyncio.run(OpenVikingClient().commit_session_v2({"session_id": "sess_ov_map"}))
|
|
|
|
|
result = asyncio.run(OpenVikingClient(mode="skeleton").commit_session_v2({"session_id": "sess_ov_map"}))
|
|
|
|
|
|
|
|
|
|
assert result.refs
|
|
|
|
|
assert result.refs[0].ref_type == MemoryRefType.SESSION_ARCHIVE
|
|
|
|
|
assert result.refs[0].native_id == "ov_session_summary:sess_ov_map"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_skeleton_multiple_refs_are_written_by_process_outbox_event():
|
|
|
|
|
def test_everos_skeleton_multiple_refs_are_written_by_process_outbox_event():
|
|
|
|
|
repo = InMemoryRepository()
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SKIPPED)
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=EverMemOSClient(),
|
|
|
|
|
everos_client=EverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
service.commit_session("sess_em_skeleton", CommitRequest(workspace_id="ws_1", user_id="user_a", agent_id="agent_cli"))
|
|
|
|
|
)
|
|
|
|
|
event = next(event for event in repo.list_outbox_events_by_job(response.job_id) if event.backend_type == BackendType.EVERMEMOS)
|
|
|
|
|
event = next(event for event in repo.list_outbox_events_by_job(response.job_id) if event.backend_type == BackendType.EVEROS)
|
|
|
|
|
|
|
|
|
|
updated = asyncio.run(service.process_outbox_event(event.id))
|
|
|
|
|
refs = repo.list_memory_refs(session_id="sess_em_skeleton", backend_type=BackendType.EVERMEMOS, status=BackendRefStatus.SUCCESS)
|
|
|
|
|
refs = repo.list_memory_refs(session_id="sess_em_skeleton", backend_type=BackendType.EVEROS, status=BackendRefStatus.SUCCESS)
|
|
|
|
|
|
|
|
|
|
assert updated.status == OutboxEventStatus.SUCCESS
|
|
|
|
|
assert len(refs) == 2
|
|
|
|
|
@ -735,18 +795,18 @@ def test_obsidian_review_adapter_skeleton_returns_skipped_write_result():
|
|
|
|
|
|
|
|
|
|
def test_backend_commit_result_supports_multiple_produced_refs():
|
|
|
|
|
result = BackendCommitResult(
|
|
|
|
|
backend_type=BackendType.EVERMEMOS,
|
|
|
|
|
backend_type=BackendType.EVEROS,
|
|
|
|
|
status=BackendResultStatus.SUCCESS,
|
|
|
|
|
refs=[
|
|
|
|
|
BackendProducedRef(ref_type=MemoryRefType.PROFILE, native_id="profile_1"),
|
|
|
|
|
BackendProducedRef(ref_type=MemoryRefType.LONG_TERM_MEMORY, native_uri="evermemos://memories/long_1"),
|
|
|
|
|
BackendProducedRef(ref_type=MemoryRefType.LONG_TERM_MEMORY, native_uri="everos://memories/long_1"),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
dumped = result.model_dump(mode="json")
|
|
|
|
|
assert len(result.refs) == 2
|
|
|
|
|
assert dumped["refs"][0]["ref_type"] == "profile"
|
|
|
|
|
assert dumped["refs"][1]["native_uri"] == "evermemos://memories/long_1"
|
|
|
|
|
assert dumped["refs"][1]["native_uri"] == "everos://memories/long_1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_backend_ref_type_mapping_and_unknown_fallback_preserves_original_type():
|
|
|
|
|
@ -758,11 +818,11 @@ def test_backend_ref_type_mapping_and_unknown_fallback_preserves_original_type()
|
|
|
|
|
assert mapped == MemoryRefType.SESSION_ARCHIVE
|
|
|
|
|
assert metadata == {}
|
|
|
|
|
|
|
|
|
|
mapped, metadata = map_backend_ref_type(BackendType.EVERMEMOS, "preference")
|
|
|
|
|
mapped, metadata = map_backend_ref_type(BackendType.EVEROS, "preference")
|
|
|
|
|
assert mapped == MemoryRefType.PROFILE
|
|
|
|
|
assert metadata == {}
|
|
|
|
|
|
|
|
|
|
mapped, metadata = map_backend_ref_type(BackendType.EVERMEMOS, "unknown_signal")
|
|
|
|
|
mapped, metadata = map_backend_ref_type(BackendType.EVEROS, "unknown_signal")
|
|
|
|
|
assert mapped == MemoryRefType.LONG_TERM_MEMORY
|
|
|
|
|
assert metadata["original_ref_type"] == "unknown_signal"
|
|
|
|
|
|
|
|
|
|
@ -798,29 +858,10 @@ def test_openviking_commit_fixture_normalizes_to_backend_commit_result_without_u
|
|
|
|
|
assert "messages" not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_backend_response_fixture_files_exist_and_load():
|
|
|
|
|
names = {
|
|
|
|
|
"openviking_ingest_success.json",
|
|
|
|
|
"openviking_ingest_real_success.json",
|
|
|
|
|
"openviking_ingest_real_error_401.json",
|
|
|
|
|
"openviking_ingest_real_error_422.json",
|
|
|
|
|
"openviking_ingest_real_error_500.json",
|
|
|
|
|
"openviking_commit_success.json",
|
|
|
|
|
"openviking_retrieve_success.json",
|
|
|
|
|
"evermemos_ingest_success.json",
|
|
|
|
|
"evermemos_commit_success_multiple_refs.json",
|
|
|
|
|
"evermemos_retrieve_success.json",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name in names:
|
|
|
|
|
payload = load_backend_fixture(name)
|
|
|
|
|
assert payload["status"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openviking_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
ingest = normalize_openviking_ingest_response(load_backend_fixture("openviking_ingest_success.json"))
|
|
|
|
|
commit = normalize_openviking_commit_response(load_backend_fixture("openviking_commit_success.json"))
|
|
|
|
|
retrieve = normalize_openviking_retrieve_response(load_backend_fixture("openviking_retrieve_success.json"))
|
|
|
|
|
ingest = normalize_openviking_ingest_response(backend_response("openviking_ingest_success.json"))
|
|
|
|
|
commit = normalize_openviking_commit_response(backend_response("openviking_commit_success.json"))
|
|
|
|
|
retrieve = normalize_openviking_retrieve_response(backend_response("openviking_retrieve_success.json"))
|
|
|
|
|
|
|
|
|
|
assert ingest.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert ingest.native_id == "ov_turn_fixture_1"
|
|
|
|
|
@ -841,7 +882,7 @@ def test_openviking_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
assert blocked not in serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_commit_fixture_normalizes_multiple_produced_refs_and_unknown_type():
|
|
|
|
|
def test_everos_commit_fixture_normalizes_multiple_produced_refs_and_unknown_type():
|
|
|
|
|
raw = {
|
|
|
|
|
"status": "success",
|
|
|
|
|
"data": {
|
|
|
|
|
@ -853,7 +894,7 @@ def test_evermemos_commit_fixture_normalizes_multiple_produced_refs_and_unknown_
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = normalize_evermemos_commit_response(raw)
|
|
|
|
|
result = normalize_everos_commit_response(raw)
|
|
|
|
|
|
|
|
|
|
assert result.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert len(result.refs) == 3
|
|
|
|
|
@ -866,10 +907,10 @@ def test_evermemos_commit_fixture_normalizes_multiple_produced_refs_and_unknown_
|
|
|
|
|
assert "SECRET_PROFILE" not in json.dumps(result.model_dump(mode="json"), ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evermemos_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
ingest = normalize_evermemos_ingest_response(load_backend_fixture("evermemos_ingest_success.json"))
|
|
|
|
|
commit = normalize_evermemos_commit_response(load_backend_fixture("evermemos_commit_success_multiple_refs.json"))
|
|
|
|
|
retrieve = normalize_evermemos_retrieve_response(load_backend_fixture("evermemos_retrieve_success.json"))
|
|
|
|
|
def test_everos_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
ingest = normalize_everos_ingest_response(backend_response("everos_ingest_success.json"))
|
|
|
|
|
commit = normalize_everos_commit_response(backend_response("everos_commit_success_multiple_refs.json"))
|
|
|
|
|
retrieve = normalize_everos_retrieve_response(backend_response("everos_retrieve_success.json"))
|
|
|
|
|
|
|
|
|
|
assert ingest.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert ingest.native_id == "em_memory_fixture_1"
|
|
|
|
|
@ -881,7 +922,7 @@ def test_evermemos_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
}
|
|
|
|
|
assert retrieve.status == BackendResultStatus.SUCCESS
|
|
|
|
|
assert len(retrieve.items) == 2
|
|
|
|
|
assert retrieve.items[0].source_backend == BackendType.EVERMEMOS
|
|
|
|
|
assert retrieve.items[0].source_backend == BackendType.EVEROS
|
|
|
|
|
assert retrieve.items[0].memory_type == "episodic_memory"
|
|
|
|
|
serialized = json.dumps(
|
|
|
|
|
{
|
|
|
|
|
@ -897,7 +938,7 @@ def test_evermemos_success_fixtures_normalize_without_unsafe_metadata():
|
|
|
|
|
|
|
|
|
|
def test_malformed_retrieve_response_returns_skipped_empty_result():
|
|
|
|
|
ov = normalize_openviking_retrieve_response({})
|
|
|
|
|
em = normalize_evermemos_retrieve_response({"data": {"unexpected": "shape"}})
|
|
|
|
|
em = normalize_everos_retrieve_response({"data": {"unexpected": "shape"}})
|
|
|
|
|
|
|
|
|
|
assert ov.status == BackendResultStatus.SKIPPED
|
|
|
|
|
assert ov.items == []
|
|
|
|
|
@ -914,7 +955,7 @@ def test_ingest_response_normalizers_return_write_results_and_sanitize_metadata(
|
|
|
|
|
"metadata": {"backend_request_id": "ov_req", "conversation": "SECRET"},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
em = normalize_evermemos_ingest_response(
|
|
|
|
|
em = normalize_everos_ingest_response(
|
|
|
|
|
{
|
|
|
|
|
"status": "success",
|
|
|
|
|
"memory_id": "em_turn_1",
|
|
|
|
|
@ -935,12 +976,12 @@ def test_ingest_response_normalizers_return_write_results_and_sanitize_metadata(
|
|
|
|
|
def test_backend_error_retryable_mapping():
|
|
|
|
|
for status_code in (429, 500, 502, 503, 504):
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.OPENVIKING, status_code=status_code) is True
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVERMEMOS, error_code="timeout") is True
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVERMEMOS, error_message="network_error: reset") is True
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVEROS, error_code="timeout") is True
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVEROS, error_message="network_error: reset") is True
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.OPENVIKING, error_code="mystery") is True
|
|
|
|
|
|
|
|
|
|
for status_code in (400, 401, 403, 404, 422):
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVERMEMOS, status_code=status_code) is False
|
|
|
|
|
assert map_backend_error_to_retryable(BackendType.EVEROS, status_code=status_code) is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_client_map_error_contracts_for_future_http_integration():
|
|
|
|
|
@ -951,8 +992,8 @@ def test_client_map_error_contracts_for_future_http_integration():
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"response {self.status_code}"
|
|
|
|
|
|
|
|
|
|
ov_client = OpenVikingClient()
|
|
|
|
|
em_client = EverMemOSClient()
|
|
|
|
|
ov_client = OpenVikingClient(mode="skeleton")
|
|
|
|
|
em_client = EverOSClient(mode="skeleton")
|
|
|
|
|
|
|
|
|
|
for status_code in (429, 500, 502, 503, 504):
|
|
|
|
|
assert ov_client._map_error(ResponseLike(status_code)) is True
|
|
|
|
|
@ -979,20 +1020,20 @@ def test_ingest_service_records_two_success_refs():
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(service.ingest_conversation_turn(IngestRequest(**build_ingest_payload())))
|
|
|
|
|
|
|
|
|
|
assert response.status == "success"
|
|
|
|
|
assert len(response.refs) == 2
|
|
|
|
|
assert {ref.backend_type.value for ref in response.refs} == {"openviking", "evermemos"}
|
|
|
|
|
assert {ref.backend_type.value for ref in response.refs} == {"openviking", "everos"}
|
|
|
|
|
assert {ref.status for ref in repo.list_memory_refs()} == {BackendRefStatus.SUCCESS}
|
|
|
|
|
assert len(repo.list_memory_refs(backend_type="openviking", status=BackendRefStatus.SUCCESS)) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_v2_ingest_service_openviking_real_mock_success_writes_safe_memory_ref():
|
|
|
|
|
fixture = load_backend_fixture("openviking_ingest_real_success.json")
|
|
|
|
|
fixture = backend_response("openviking_ingest_real_success.json")
|
|
|
|
|
|
|
|
|
|
def handler(request):
|
|
|
|
|
payload = json.loads(request.content.decode())
|
|
|
|
|
@ -1012,7 +1053,7 @@ def test_v2_ingest_service_openviking_real_mock_success_writes_safe_memory_ref()
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=real_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1034,10 +1075,10 @@ def test_v2_ingest_service_openviking_real_mock_success_writes_safe_memory_ref()
|
|
|
|
|
assert "ov-super-secret-token" not in audit_json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_v2_ingest_service_real_mock_success_writes_openviking_and_evermemos_refs_safely():
|
|
|
|
|
ov_fixture = load_backend_fixture("openviking_ingest_real_success.json")
|
|
|
|
|
em_fixture = load_backend_fixture("evermemos_ingest_success.json")
|
|
|
|
|
seen = {"openviking": 0, "evermemos": 0}
|
|
|
|
|
def test_v2_ingest_service_real_mock_success_writes_openviking_and_everos_refs_safely():
|
|
|
|
|
ov_fixture = backend_response("openviking_ingest_real_success.json")
|
|
|
|
|
em_fixture = backend_response("everos_ingest_success.json")
|
|
|
|
|
seen = {"openviking": 0, "everos": 0}
|
|
|
|
|
|
|
|
|
|
def openviking_handler(request):
|
|
|
|
|
payload = json.loads(request.content.decode())
|
|
|
|
|
@ -1046,12 +1087,12 @@ def test_v2_ingest_service_real_mock_success_writes_openviking_and_evermemos_ref
|
|
|
|
|
seen["openviking"] += 1
|
|
|
|
|
return httpx.Response(200, json=ov_fixture)
|
|
|
|
|
|
|
|
|
|
def evermemos_handler(request):
|
|
|
|
|
def everos_handler(request):
|
|
|
|
|
payload = json.loads(request.content.decode())
|
|
|
|
|
assert payload["content"] == "SECRET_DUAL_REAL_CONTENT"
|
|
|
|
|
assert payload["messages"][0]["content"] == "SECRET_DUAL_REAL_CONTENT"
|
|
|
|
|
assert request.headers["x-api-key"] == "em-dual-token"
|
|
|
|
|
assert request.headers["authorization"] == "Bearer em-dual-token"
|
|
|
|
|
seen["evermemos"] += 1
|
|
|
|
|
seen["everos"] += 1
|
|
|
|
|
return httpx.Response(200, json=em_fixture)
|
|
|
|
|
|
|
|
|
|
async def real_openviking_factory():
|
|
|
|
|
@ -1066,11 +1107,11 @@ def test_v2_ingest_service_real_mock_success_writes_openviking_and_evermemos_ref
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=real_openviking_factory,
|
|
|
|
|
evermemos_client=EverMemOSClient(
|
|
|
|
|
everos_client=EverOSClient(
|
|
|
|
|
mode="real",
|
|
|
|
|
base_url="http://evermemos.test",
|
|
|
|
|
base_url="http://everos.test",
|
|
|
|
|
api_key="em-dual-token",
|
|
|
|
|
transport=httpx.MockTransport(evermemos_handler),
|
|
|
|
|
transport=httpx.MockTransport(everos_handler),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -1092,8 +1133,8 @@ def test_v2_ingest_service_real_mock_success_writes_openviking_and_evermemos_ref
|
|
|
|
|
audit_json = json.dumps([entry.model_dump(mode="json") for entry in repo.list_audit()], ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
assert response.status == OperationStatus.SUCCESS
|
|
|
|
|
assert seen == {"openviking": 1, "evermemos": 1}
|
|
|
|
|
assert {ref.backend_type for ref in refs} == {BackendType.OPENVIKING, BackendType.EVERMEMOS}
|
|
|
|
|
assert seen == {"openviking": 1, "everos": 1}
|
|
|
|
|
assert {ref.backend_type for ref in refs} == {BackendType.OPENVIKING, BackendType.EVEROS}
|
|
|
|
|
assert {ref.status for ref in refs} == {BackendRefStatus.SUCCESS}
|
|
|
|
|
assert {ref.content_hash for ref in refs}
|
|
|
|
|
assert "trace_dual_real" in serialized_refs
|
|
|
|
|
@ -1108,7 +1149,7 @@ def test_ingest_service_backend_failure_is_partial_success():
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FailingEverMemOSClient(),
|
|
|
|
|
everos_client=FailingEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(service.ingest_conversation_turn(IngestRequest(**build_ingest_payload())))
|
|
|
|
|
@ -1117,8 +1158,8 @@ def test_ingest_service_backend_failure_is_partial_success():
|
|
|
|
|
assert len(response.refs) == 2
|
|
|
|
|
failed = [ref for ref in response.refs if ref.status == BackendRefStatus.FAILED]
|
|
|
|
|
assert len(failed) == 1
|
|
|
|
|
assert failed[0].backend_type.value == "evermemos"
|
|
|
|
|
assert "evermemos unavailable" in failed[0].error_message
|
|
|
|
|
assert failed[0].backend_type.value == "everos"
|
|
|
|
|
assert "everos unavailable" in failed[0].error_message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ingest_service_records_two_skipped_refs_when_policy_disables_backends():
|
|
|
|
|
@ -1126,7 +1167,7 @@ def test_ingest_service_records_two_skipped_refs_when_policy_disables_backends()
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1135,7 +1176,7 @@ def test_ingest_service_records_two_skipped_refs_when_policy_disables_backends()
|
|
|
|
|
**build_ingest_payload(
|
|
|
|
|
policy={
|
|
|
|
|
"allow_openviking": False,
|
|
|
|
|
"allow_evermemos": False,
|
|
|
|
|
"allow_everos": False,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
@ -1153,7 +1194,7 @@ def test_duplicate_idempotency_key_upserts_memory_refs_without_duplicates():
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
first = asyncio.run(
|
|
|
|
|
@ -1185,7 +1226,7 @@ def test_memory_ref_metadata_does_not_store_conversation_content_or_raw_request(
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
sensitive_content = "SECRET_CONVERSATION_CONTENT_SHOULD_NOT_BE_STORED"
|
|
|
|
|
|
|
|
|
|
@ -1213,7 +1254,7 @@ def test_sqlite_repository_persists_v2_memory_refs(tmp_path):
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
asyncio.run(service.ingest_conversation_turn(IngestRequest(**build_ingest_payload(turn_id="turn_sqlite"))))
|
|
|
|
|
@ -1253,7 +1294,7 @@ def test_commit_session_creates_commit_job_and_outbox_events():
|
|
|
|
|
assert job.session_id == "sess_commit"
|
|
|
|
|
assert job.status.value == "accepted"
|
|
|
|
|
assert len(events) == 2
|
|
|
|
|
assert {event.backend_type for event in events} == {BackendType.OPENVIKING, BackendType.EVERMEMOS}
|
|
|
|
|
assert {event.backend_type for event in events} == {BackendType.OPENVIKING, BackendType.EVEROS}
|
|
|
|
|
assert {event.operation for event in events} == {BackendOperation.COMMIT_SESSION}
|
|
|
|
|
assert {event.status for event in events} == {OutboxEventStatus.PENDING}
|
|
|
|
|
|
|
|
|
|
@ -1336,7 +1377,7 @@ def test_retrieve_response_contract_contains_items_refs_conflicts_trace_id_statu
|
|
|
|
|
service = MemoryGatewayV2Service(
|
|
|
|
|
repo=repo,
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
asyncio.run(service.ingest_conversation_turn(IngestRequest(**build_ingest_payload())))
|
|
|
|
|
|
|
|
|
|
@ -1357,8 +1398,14 @@ def test_retrieve_response_contract_contains_items_refs_conflicts_trace_id_statu
|
|
|
|
|
assert set(["items", "refs", "conflicts", "trace_id", "status"]).issubset(dumped)
|
|
|
|
|
assert response.trace_id == "trace_1"
|
|
|
|
|
assert response.status.value == "success"
|
|
|
|
|
assert len(response.items) == len(response.refs)
|
|
|
|
|
assert [item.source_backend for item in response.items] == [BackendType.EVEROS, BackendType.OPENVIKING]
|
|
|
|
|
assert [item.score for item in response.items] == [0.91, 0.82]
|
|
|
|
|
assert len(response.refs) == 2
|
|
|
|
|
assert response.conflicts == []
|
|
|
|
|
assert response.metadata["backend_results"] == [
|
|
|
|
|
{"backend_type": "openviking", "status": "success", "items": 1, "error_code": None, "error_message": None},
|
|
|
|
|
{"backend_type": "everos", "status": "success", "items": 1, "error_code": None, "error_message": None},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_commit_job_success_updates_job_and_writes_memory_refs():
|
|
|
|
|
@ -1368,8 +1415,8 @@ def test_process_commit_job_success_updates_job_and_writes_memory_refs():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_commit_1", native_uri="viking://sessions/sess_commit")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SUCCESS, native_id="em_commit_1", native_uri="evermemos://memories/em_commit_1")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SUCCESS, native_id="em_commit_1", native_uri="everos://memories/em_commit_1")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1389,7 +1436,7 @@ def test_process_commit_job_success_updates_job_and_writes_memory_refs():
|
|
|
|
|
assert job.created_refs_count == 2
|
|
|
|
|
assert {event.status for event in events} == {OutboxEventStatus.SUCCESS}
|
|
|
|
|
assert len(refs) == 2
|
|
|
|
|
assert {ref.backend_type for ref in refs} == {BackendType.OPENVIKING, BackendType.EVERMEMOS}
|
|
|
|
|
assert {ref.backend_type for ref in refs} == {BackendType.OPENVIKING, BackendType.EVEROS}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_outbox_event_writes_multiple_produced_memory_refs():
|
|
|
|
|
@ -1529,8 +1576,8 @@ def test_process_commit_job_one_success_one_failed_is_partial_success():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_commit_1")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.FAILED, retryable=False, error_message="evermemos failed")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.FAILED, retryable=False, error_message="everos failed")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1542,7 +1589,7 @@ def test_process_commit_job_one_success_one_failed_is_partial_success():
|
|
|
|
|
|
|
|
|
|
assert job.status.value == "partial_success"
|
|
|
|
|
assert job.created_refs_count == 1
|
|
|
|
|
assert "evermemos failed" in job.error_message
|
|
|
|
|
assert "everos failed" in job.error_message
|
|
|
|
|
assert {event.status for event in events} == {OutboxEventStatus.SUCCESS, OutboxEventStatus.DEAD_LETTER}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1553,8 +1600,8 @@ def test_process_commit_job_two_failed_is_failed():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.FAILED, retryable=False, error_message="openviking failed")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.FAILED, retryable=False, error_message="evermemos failed")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.FAILED, retryable=False, error_message="everos failed")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1566,7 +1613,7 @@ def test_process_commit_job_two_failed_is_failed():
|
|
|
|
|
assert job.status.value == "failed"
|
|
|
|
|
assert job.created_refs_count == 0
|
|
|
|
|
assert "openviking failed" in job.error_message
|
|
|
|
|
assert "evermemos failed" in job.error_message
|
|
|
|
|
assert "everos failed" in job.error_message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_retryable_failed_outbox_event_requeues_with_next_retry():
|
|
|
|
|
@ -1597,8 +1644,8 @@ def test_process_pending_outbox_events_processes_pending_batch():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_commit_1")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SUCCESS, native_id="em_commit_1")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SUCCESS, native_id="em_commit_1")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
asyncio.run(
|
|
|
|
|
@ -1641,8 +1688,8 @@ def test_commit_pipeline_metadata_does_not_store_content_or_raw_request():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_commit_1")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SUCCESS, native_id="em_commit_1")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SUCCESS, native_id="em_commit_1")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
sensitive_content = "SECRET_COMMIT_PIPELINE_CONTENT_SHOULD_NOT_BE_STORED"
|
|
|
|
|
@ -1728,8 +1775,8 @@ def test_process_pending_outbox_events_uses_claim_and_does_not_process_existing_
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_claimed")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SUCCESS, native_id="em_claimed")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SUCCESS, native_id="em_claimed")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1754,8 +1801,8 @@ def test_terminal_outbox_statuses_clear_lock_fields():
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_lock_clear")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SKIPPED)
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SKIPPED)
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
response = asyncio.run(
|
|
|
|
|
@ -1848,8 +1895,8 @@ def test_admin_process_outbox_endpoint_triggers_pending_processing(monkeypatch):
|
|
|
|
|
openviking_client_factory=fake_commit_openviking_factory(
|
|
|
|
|
commit_result(BackendType.OPENVIKING, BackendResultStatus.SUCCESS, native_id="ov_admin")
|
|
|
|
|
),
|
|
|
|
|
evermemos_client=FakeCommitEverMemOSClient(
|
|
|
|
|
commit_result(BackendType.EVERMEMOS, BackendResultStatus.SUCCESS, native_id="em_admin")
|
|
|
|
|
everos_client=FakeCommitEverOSClient(
|
|
|
|
|
commit_result(BackendType.EVEROS, BackendResultStatus.SUCCESS, native_id="em_admin")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
asyncio.run(
|
|
|
|
|
@ -1907,7 +1954,7 @@ def test_v2_ingest_router_accepts_legal_request(monkeypatch):
|
|
|
|
|
api_v2.v2_service = MemoryGatewayV2Service(
|
|
|
|
|
repo=InMemoryRepository(),
|
|
|
|
|
openviking_client_factory=fake_openviking_factory,
|
|
|
|
|
evermemos_client=FakeEverMemOSClient(),
|
|
|
|
|
everos_client=FakeEverOSClient(),
|
|
|
|
|
)
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
app.dependency_overrides[verify_api_key_compat] = lambda: None
|
|
|
|
|
|