@@ -1997,6 +1997,205 @@ def test_checkpoint_registered_in_tools(self):
19971997 assert "mempalace_checkpoint" in mcp_server .TOOLS
19981998 assert mcp_server .TOOLS ["mempalace_checkpoint" ]["handler" ] is mcp_server .tool_checkpoint
19991999
2000+ def test_checkpoint_added_by_defaults_to_diary_agent (
2001+ self , monkeypatch , config , palace_path , kg
2002+ ):
2003+ """#2023: with no explicit ``added_by``, each filed drawer is attributed
2004+ to the diary ``agent_name`` (verbatim case) rather than the generic
2005+ ``checkpoint`` label, so the filing agent survives in provenance."""
2006+ _patch_mcp_server (monkeypatch , config , kg )
2007+ _client , _col = _get_collection (palace_path , create = True )
2008+ _client .close () # release file handles; a bare del leaks them on Windows (#1128)
2009+ from mempalace .mcp_server import tool_checkpoint
2010+
2011+ result = tool_checkpoint (
2012+ items = [{"wing" : "w" , "room" : "decisions" , "content" : "Use PostgreSQL for storage." }],
2013+ diary = {"agent_name" : "DeepSeek" , "wing" : "w" , "entry" : "SESSION|did.stuff|star" },
2014+ )
2015+ assert len (result ["added" ]) == 1
2016+
2017+ client , col = _get_collection (palace_path )
2018+ try :
2019+ metas = col .get (include = ["metadatas" ])["metadatas" ]
2020+ finally :
2021+ client .close ()
2022+ drawers = [m for m in metas if m .get ("room" ) == "decisions" ]
2023+ assert len (drawers ) == 1
2024+ # Verbatim case, not the lowercased diary-index form of agent_name.
2025+ assert drawers [0 ]["added_by" ] == "DeepSeek"
2026+
2027+ def test_checkpoint_explicit_added_by_overrides_diary (self , monkeypatch ):
2028+ """An explicit ``added_by`` wins over the diary ``agent_name`` fallback."""
2029+ from mempalace import mcp_server
2030+
2031+ monkeypatch .setattr (
2032+ mcp_server , "tool_check_duplicate" , lambda * a , ** k : {"is_duplicate" : False }
2033+ )
2034+ monkeypatch .setattr (mcp_server , "tool_diary_write" , lambda ** k : {"success" : True })
2035+ filed = {}
2036+
2037+ def _add (** kwargs ):
2038+ filed .update (kwargs )
2039+ return {"success" : True , "drawer_id" : "d1" }
2040+
2041+ monkeypatch .setattr (mcp_server , "tool_add_drawer" , _add )
2042+
2043+ mcp_server .tool_checkpoint (
2044+ items = [{"wing" : "w" , "room" : "r" , "content" : "keep me" }],
2045+ diary = {"agent_name" : "deepseek" , "entry" : "SESSION|x|star" },
2046+ added_by = "alice" ,
2047+ )
2048+ assert filed ["added_by" ] == "alice"
2049+
2050+ def test_checkpoint_added_by_falls_back_to_checkpoint_label (self , monkeypatch ):
2051+ """Neither an explicit ``added_by`` nor a diary ``agent_name`` -> the
2052+ drawer keeps the legacy ``checkpoint`` attribution (backward compatible)."""
2053+ from mempalace import mcp_server
2054+
2055+ monkeypatch .setattr (
2056+ mcp_server , "tool_check_duplicate" , lambda * a , ** k : {"is_duplicate" : False }
2057+ )
2058+ monkeypatch .setattr (mcp_server , "tool_diary_write" , lambda ** k : {"success" : True })
2059+ seen = []
2060+
2061+ def _add (** kwargs ):
2062+ seen .append (kwargs ["added_by" ])
2063+ return {"success" : True , "drawer_id" : "d1" }
2064+
2065+ monkeypatch .setattr (mcp_server , "tool_add_drawer" , _add )
2066+
2067+ # No diary block at all.
2068+ mcp_server .tool_checkpoint (items = [{"wing" : "w" , "room" : "r" , "content" : "a" }])
2069+ # Diary present but without an ``agent_name``.
2070+ mcp_server .tool_checkpoint (
2071+ items = [{"wing" : "w" , "room" : "r" , "content" : "b" }],
2072+ diary = {"entry" : "SESSION|y|star" },
2073+ )
2074+ assert seen == ["checkpoint" , "checkpoint" ]
2075+
2076+ def test_checkpoint_added_by_accepted_via_dispatch (self , monkeypatch ):
2077+ """#2023: ``added_by`` passes the tools/call schema whitelist (the
2078+ reporter's HTTP MCP transport reuses this dispatcher) and the real
2079+ handler forwards it, for both the explicit value and the diary fallback."""
2080+ from mempalace import mcp_server
2081+
2082+ monkeypatch .setattr (
2083+ mcp_server , "tool_check_duplicate" , lambda * a , ** k : {"is_duplicate" : False }
2084+ )
2085+ monkeypatch .setattr (mcp_server , "tool_diary_write" , lambda ** k : {"success" : True })
2086+ filed = {}
2087+
2088+ def _add (** kwargs ):
2089+ filed .update (kwargs )
2090+ return {"success" : True , "drawer_id" : "d1" }
2091+
2092+ monkeypatch .setattr (mcp_server , "tool_add_drawer" , _add )
2093+
2094+ resp = mcp_server .handle_request (
2095+ {
2096+ "method" : "tools/call" ,
2097+ "id" : 1 ,
2098+ "params" : {
2099+ "name" : "mempalace_checkpoint" ,
2100+ "arguments" : {
2101+ "items" : [{"wing" : "w" , "room" : "r" , "content" : "hi" }],
2102+ "added_by" : "alice" ,
2103+ },
2104+ },
2105+ }
2106+ )
2107+ assert "error" not in resp
2108+ assert filed ["added_by" ] == "alice"
2109+
2110+ filed .clear ()
2111+ resp2 = mcp_server .handle_request (
2112+ {
2113+ "method" : "tools/call" ,
2114+ "id" : 2 ,
2115+ "params" : {
2116+ "name" : "mempalace_checkpoint" ,
2117+ "arguments" : {
2118+ "items" : [{"wing" : "w" , "room" : "r" , "content" : "yo" }],
2119+ "diary" : {"agent_name" : "DeepSeek" , "entry" : "SESSION|z|star" },
2120+ },
2121+ },
2122+ }
2123+ )
2124+ assert "error" not in resp2
2125+ assert filed ["added_by" ] == "DeepSeek"
2126+
2127+ def test_checkpoint_schema_exposes_added_by (self ):
2128+ """``added_by`` is declared in the checkpoint tool schema so the
2129+ dispatch whitelist admits it instead of rejecting it as unknown."""
2130+ from mempalace import mcp_server
2131+
2132+ props = mcp_server .TOOLS ["mempalace_checkpoint" ]["input_schema" ]["properties" ]
2133+ assert "added_by" in props
2134+ assert props ["added_by" ]["type" ] == "string"
2135+
2136+ def test_checkpoint_blank_or_invalid_added_by_defers_to_diary (self , monkeypatch ):
2137+ """A blank, whitespace-only, non-string, or None explicit ``added_by``
2138+ counts as unspecified, so it defers to the diary ``agent_name`` rather
2139+ than masking it; with no usable diary name it falls to ``checkpoint``."""
2140+ from mempalace import mcp_server
2141+
2142+ monkeypatch .setattr (
2143+ mcp_server , "tool_check_duplicate" , lambda * a , ** k : {"is_duplicate" : False }
2144+ )
2145+ monkeypatch .setattr (mcp_server , "tool_diary_write" , lambda ** k : {"success" : True })
2146+ seen = []
2147+
2148+ def _add (** kwargs ):
2149+ seen .append (kwargs ["added_by" ])
2150+ return {"success" : True , "drawer_id" : "d1" }
2151+
2152+ monkeypatch .setattr (mcp_server , "tool_add_drawer" , _add )
2153+
2154+ diary = {"agent_name" : "deepseek" , "entry" : "SESSION|x|star" }
2155+ for bad in ("" , " " , 123 , None ):
2156+ mcp_server .tool_checkpoint (
2157+ items = [{"wing" : "w" , "room" : "r" , "content" : f"c{ bad !r} " }],
2158+ diary = diary ,
2159+ added_by = bad ,
2160+ )
2161+ # Every unusable explicit value defers to the diary agent.
2162+ assert seen == ["deepseek" , "deepseek" , "deepseek" , "deepseek" ]
2163+
2164+ # Blank explicit AND a blank diary name -> the legacy label.
2165+ seen .clear ()
2166+ mcp_server .tool_checkpoint (
2167+ items = [{"wing" : "w" , "room" : "r" , "content" : "z" }],
2168+ diary = {"agent_name" : " " , "entry" : "SESSION|y|star" },
2169+ added_by = "" ,
2170+ )
2171+ assert seen == ["checkpoint" ]
2172+
2173+ def test_checkpoint_added_by_uniform_across_items (self , monkeypatch ):
2174+ """All items in one checkpoint share a single resolved author (a
2175+ checkpoint is one agent's session save; attribution is resolved once)."""
2176+ from mempalace import mcp_server
2177+
2178+ monkeypatch .setattr (
2179+ mcp_server , "tool_check_duplicate" , lambda * a , ** k : {"is_duplicate" : False }
2180+ )
2181+ monkeypatch .setattr (mcp_server , "tool_diary_write" , lambda ** k : {"success" : True })
2182+ seen = []
2183+
2184+ def _add (** kwargs ):
2185+ seen .append (kwargs ["added_by" ])
2186+ return {"success" : True , "drawer_id" : kwargs ["content" ]}
2187+
2188+ monkeypatch .setattr (mcp_server , "tool_add_drawer" , _add )
2189+
2190+ mcp_server .tool_checkpoint (
2191+ items = [
2192+ {"wing" : "w" , "room" : "r" , "content" : "one" },
2193+ {"wing" : "w" , "room" : "r" , "content" : "two" },
2194+ ],
2195+ diary = {"agent_name" : "DeepSeek" , "entry" : "SESSION|q|star" },
2196+ )
2197+ assert seen == ["DeepSeek" , "DeepSeek" ]
2198+
20002199 def test_get_drawer (self , monkeypatch , config , palace_path , seeded_collection , kg ):
20012200 _patch_mcp_server (monkeypatch , config , kg )
20022201 from mempalace .mcp_server import tool_get_drawer
0 commit comments