Initial release: iai-mcp v0.1.0
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: XNLLLLH <XNLLLLH@users.noreply.github.com>
This commit is contained in:
commit
f6b876fbe7
332 changed files with 97258 additions and 0 deletions
153
tests/test_invariant_anchor_edges.py
Normal file
153
tests/test_invariant_anchor_edges.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"""Tests for the invariant_anchor edge type (MEM-09, D-22).
|
||||
|
||||
invariant_anchor edges are the structural marker of S5 identity commitments:
|
||||
- created when propose_invariant_update reaches 3-of-5 consensus
|
||||
- src = original anchor record; dst = new consensus record
|
||||
- NEVER decayed by the FSRS sweep (sleep._decay_edges filters hebbian only)
|
||||
- At most 1 edge per 48h cooldown window (D-22 prevents rapid poisoning)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from iai_mcp.types import EMBED_DIM, MemoryRecord
|
||||
|
||||
|
||||
def _anchor(s5_trust_score: float = 0.9) -> MemoryRecord:
|
||||
now = datetime.now(timezone.utc)
|
||||
return MemoryRecord(
|
||||
id=uuid4(),
|
||||
tier="semantic",
|
||||
literal_surface="User identity: Alice",
|
||||
aaak_index="",
|
||||
embedding=[1.0] + [0.0] * (EMBED_DIM - 1),
|
||||
community_id=None,
|
||||
centrality=0.0,
|
||||
detail_level=5,
|
||||
pinned=True,
|
||||
stability=0.5,
|
||||
difficulty=0.3,
|
||||
last_reviewed=now,
|
||||
never_decay=True,
|
||||
never_merge=True,
|
||||
provenance=[],
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
tags=["identity"],
|
||||
language="en",
|
||||
s5_trust_score=s5_trust_score,
|
||||
)
|
||||
|
||||
|
||||
class _FakeEmbedder:
|
||||
DIM = EMBED_DIM
|
||||
|
||||
def embed(self, text):
|
||||
return [1.0] + [0.0] * (EMBED_DIM - 1)
|
||||
|
||||
def embed_batch(self, texts):
|
||||
return [self.embed(t) for t in texts]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _patch_embedder(monkeypatch):
|
||||
from iai_mcp import embed as embed_mod
|
||||
|
||||
monkeypatch.setattr(embed_mod, "Embedder", _FakeEmbedder)
|
||||
yield
|
||||
|
||||
|
||||
def _reach_consensus(store, anchor_id):
|
||||
"""Helper: run 3 proposals so we land on a commit."""
|
||||
from iai_mcp.s5 import propose_invariant_update
|
||||
|
||||
propose_invariant_update(store, anchor_id, "fact", "s1")
|
||||
propose_invariant_update(store, anchor_id, "fact", "s2")
|
||||
return propose_invariant_update(store, anchor_id, "fact", "s3")
|
||||
|
||||
|
||||
def test_invariant_anchor_edge_on_s5_promotion(tmp_path):
|
||||
"""After consensus commit, an invariant_anchor edge exists from anchor to
|
||||
the new consensus record."""
|
||||
from iai_mcp.store import EDGES_TABLE, MemoryStore
|
||||
|
||||
store = MemoryStore(path=tmp_path)
|
||||
anchor = _anchor()
|
||||
store.insert(anchor)
|
||||
verdict, new_id = _reach_consensus(store, anchor.id)
|
||||
assert verdict == "committed"
|
||||
assert new_id is not None
|
||||
|
||||
df = store.db.open_table(EDGES_TABLE).to_pandas()
|
||||
ia = df[df["edge_type"] == "invariant_anchor"]
|
||||
assert len(ia) >= 1
|
||||
|
||||
ids = {str(anchor.id), str(new_id)}
|
||||
# boost_edges canonicalises (src,dst) as sorted, so src/dst may be either.
|
||||
found = any(
|
||||
{str(row["src"]), str(row["dst"])} == ids
|
||||
for _, row in ia.iterrows()
|
||||
)
|
||||
assert found
|
||||
|
||||
|
||||
def test_invariant_anchor_edge_never_decays(tmp_path):
|
||||
"""invariant_anchor edges survive FSRS decay sweep indefinitely."""
|
||||
from iai_mcp.sleep import _decay_edges
|
||||
from iai_mcp.store import EDGES_TABLE, MemoryStore
|
||||
|
||||
store = MemoryStore(path=tmp_path)
|
||||
anchor = _anchor()
|
||||
store.insert(anchor)
|
||||
_reach_consensus(store, anchor.id)
|
||||
|
||||
# Artificially age the invariant_anchor edge to 500 days old with tiny weight.
|
||||
edges_tbl = store.db.open_table(EDGES_TABLE)
|
||||
df = edges_tbl.to_pandas()
|
||||
ia_rows = df[df["edge_type"] == "invariant_anchor"]
|
||||
assert not ia_rows.empty
|
||||
first = ia_rows.iloc[0]
|
||||
from datetime import timedelta as _td
|
||||
old_ts = datetime.now(timezone.utc) - _td(days=500)
|
||||
edges_tbl.update(
|
||||
where=(
|
||||
f"src = '{first['src']}' AND dst = '{first['dst']}' "
|
||||
f"AND edge_type = 'invariant_anchor'"
|
||||
),
|
||||
values={"weight": 0.001, "updated_at": old_ts},
|
||||
)
|
||||
|
||||
# Run decay sweep
|
||||
_decay_edges(store)
|
||||
|
||||
# invariant_anchor row still present
|
||||
df2 = store.db.open_table(EDGES_TABLE).to_pandas()
|
||||
survivors = df2[df2["edge_type"] == "invariant_anchor"]
|
||||
assert not survivors.empty
|
||||
|
||||
|
||||
def test_invariant_anchor_edge_no_duplicate_within_cooldown(tmp_path):
|
||||
"""Second consensus attempt within 48h returns cooldown -> no new edge."""
|
||||
from iai_mcp.store import EDGES_TABLE, MemoryStore
|
||||
|
||||
store = MemoryStore(path=tmp_path)
|
||||
anchor = _anchor()
|
||||
store.insert(anchor)
|
||||
_reach_consensus(store, anchor.id)
|
||||
|
||||
df_after_first = store.db.open_table(EDGES_TABLE).to_pandas()
|
||||
ia_first = df_after_first[df_after_first["edge_type"] == "invariant_anchor"]
|
||||
count_first = len(ia_first)
|
||||
|
||||
# Try for a second consensus -- all should be blocked by cooldown
|
||||
from iai_mcp.s5 import propose_invariant_update
|
||||
|
||||
verdict, _ = propose_invariant_update(store, anchor.id, "another", "s4")
|
||||
assert verdict == "cooldown"
|
||||
|
||||
df_after_second = store.db.open_table(EDGES_TABLE).to_pandas()
|
||||
ia_second = df_after_second[df_after_second["edge_type"] == "invariant_anchor"]
|
||||
assert len(ia_second) == count_first
|
||||
Loading…
Add table
Add a link
Reference in a new issue