chore: initialize EverOS 1.0.0

md-first memory extraction framework for AI agents.

Markdown is the single source of truth; SQLite holds state and LanceDB
provides the rebuildable vector + BM25 + scalar index. The codebase follows
a single-direction DDD layering (entrypoints -> service -> memory -> infra,
with component / core / config cross-cutting) enforced by import-linter.

Engineering surface:
- Coding conventions in .claude/rules/ (path-scoped) and workflows in
  .claude/skills/ (/commit, /new-branch, /pr).
- GitHub Actions CI runs make lint + test + integration; pre-commit mirrors
  the gates locally (ruff, hygiene hooks, gitlint commit-msg).
- Commit messages follow Conventional Commits, enforced by gitlint.
- make lint also enforces datetime two-zone discipline and OpenAPI drift.
This commit is contained in:
Elliot Chen
2026-06-05 22:35:51 +08:00
commit 518b8eca85
636 changed files with 160553 additions and 0 deletions

View File

@ -0,0 +1,177 @@
"""Tests for ``memory.get.dto``.
Pydantic-side guarantees the manager / route can rely on:
* ``GetRequest`` defaults match the wiki spec (``page=1`` /
``page_size=20`` / ``sort_by="timestamp"`` / ``sort_order="desc"``)
* ``page_size`` upper bound (1100)
* ``owner_type`` × ``memory_type`` strict pairing
* Unknown fields on the request are rejected (``extra="forbid"``)
Filter DSL coverage lives in ``test_memory/test_search/test_filters.py``
since ``/get`` shares :class:`everos.memory.search.FilterNode`.
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from everos.memory.get.dto import (
GetMemoryType,
GetRequest,
)
# ── GetRequest defaults / shape ──────────────────────────────────────────
def test_get_request_defaults_match_wiki() -> None:
"""``page`` / ``page_size`` / ``sort_by`` / ``sort_order`` come from the wiki."""
req = GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
)
assert req.page == 1
assert req.page_size == 20
assert req.sort_by == "timestamp"
assert req.sort_order == "desc"
assert req.filters is None
def test_get_request_page_size_upper_bound() -> None:
"""101 → ValidationError (wiki cap is 100)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page_size=101,
)
def test_get_request_page_size_lower_bound() -> None:
"""0 → ValidationError (page_size ≥ 1)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page_size=0,
)
def test_get_request_page_lower_bound() -> None:
"""0 → ValidationError (page ≥ 1; 1-indexed)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page=0,
)
def test_get_request_rejects_unknown_field() -> None:
"""``extra='forbid'`` — typos surface as a 422, not silent drops."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
unknown_extra=True, # type: ignore[call-arg]
)
def test_get_request_rejects_empty_user_id() -> None:
"""``user_id`` carries ``min_length=1`` — empty string is 422."""
with pytest.raises(ValidationError):
GetRequest(
user_id="",
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_missing_memory_type() -> None:
"""``memory_type`` is required — omission is 422."""
with pytest.raises(ValidationError):
GetRequest( # type: ignore[call-arg]
user_id="u1",
)
def test_get_request_rejects_missing_owner_identity() -> None:
"""Neither ``user_id`` nor ``agent_id`` → xor validator rejects."""
with pytest.raises(ValidationError, match="exactly one of"):
GetRequest( # type: ignore[call-arg]
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_both_user_and_agent_id() -> None:
"""Both ``user_id`` and ``agent_id`` set → xor validator rejects."""
with pytest.raises(ValidationError, match="exactly one of"):
GetRequest(
user_id="u1",
agent_id="agent_x",
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_invalid_memory_type_value() -> None:
"""A value outside the four-kind enum is 422."""
with pytest.raises(ValidationError):
GetRequest.model_validate(
{
"user_id": "u1",
"memory_type": "atomic_fact", # not a top-level kind
}
)
def test_get_request_rejects_invalid_sort_order() -> None:
"""``sort_order`` is a tight Literal — typos / casing variants are 422."""
with pytest.raises(ValidationError):
GetRequest.model_validate(
{
"user_id": "u1",
"memory_type": "episode",
"sort_order": "DESC", # must be lowercase
}
)
# ── owner_type × memory_type pairing ─────────────────────────────────────
@pytest.mark.parametrize(
"id_field, memory_type",
[
("user_id", GetMemoryType.EPISODE),
("user_id", GetMemoryType.PROFILE),
("agent_id", GetMemoryType.AGENT_CASE),
("agent_id", GetMemoryType.AGENT_SKILL),
],
)
def test_get_request_allows_valid_owner_memory_pair(
id_field: str,
memory_type: GetMemoryType,
) -> None:
"""The four valid (owner-kind, memory_type) combinations."""
req = GetRequest(**{id_field: "u1"}, memory_type=memory_type)
assert req.memory_type is memory_type
expected_owner_type = "user" if id_field == "user_id" else "agent"
assert req.owner_type == expected_owner_type
@pytest.mark.parametrize(
"id_field, memory_type",
[
("user_id", GetMemoryType.AGENT_CASE),
("user_id", GetMemoryType.AGENT_SKILL),
("agent_id", GetMemoryType.EPISODE),
("agent_id", GetMemoryType.PROFILE),
],
)
def test_get_request_rejects_cross_owner_memory_pair(
id_field: str,
memory_type: GetMemoryType,
) -> None:
"""Cross-pairs (user_id+agent_case etc.) are 422 at the DTO layer."""
with pytest.raises(ValidationError):
GetRequest(**{id_field: "u1"}, memory_type=memory_type)