@furongjun1999/dsh-memory 0.7.5 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -15
- package/docs/eval/issue64_replay_check_/346/240/270/351/252/214/344/270/216/344/277/256/345/244/215_v1.0.md +284 -0
- package/docs/eval/issue65_session_recall_/344/274/232/350/257/235/351/232/224/347/246/273_/346/240/270/351/252/214/344/270/216/344/277/256/345/244/215_v1.0.md +329 -0
- package/docs/eval//345/217/221/345/270/20313_/344/270/212/344/270/213/346/226/207/350/207/252/347/256/241/347/220/206/346/234/272/345/210/266_v1.0.md +2 -2
- package/docs/eval//345/217/221/345/270/20314_/350/272/253/344/275/223/303/227/350/204/221/347/273/204/345/220/210_v1.0.md +160 -0
- package/docs/eval//345/217/221/345/270/20315_/344/274/232/350/257/235/351/232/224/347/246/273/344/277/256/345/244/215/344/270/216/350/272/253/344/275/223/345/272/223/345/261/225/347/244/272_v1.0.md +88 -0
- package/docs/eval//351/262/270/345/250/230/344/270/203/347/261/273/347/212/266/346/200/201/350/277/275/350/270/252_v1.0.md +176 -0
- package/docs/mdcg//344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/344/275/277/347/224/250/344/270/216/350/277/220/347/273/264_v1.0.md +92 -13
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +43 -43
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +14 -3
- package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +9 -4
- package/docs/plans//347/201/265/346/236/242/350/272/253/344/275/223/303/227/350/204/221_/344/270/226/347/225/214/346/250/241/345/236/213/345/257/271/346/216/245/350/256/276/350/256/241_v0.1.md +45 -2
- package/md_cg/chain.py +23 -7
- package/md_cg/consolidate.py +81 -10
- package/md_cg/lifecycle.py +7 -0
- package/md_cg/mcp_server.py +22 -0
- package/md_cg/mdcg.py +6 -1
- package/md_cg/mdcos.py +24 -1
- package/md_cg/scrub.py +4 -2
- package/md_cg/sleep.py +60 -10
- package/md_cg/test_lifecycle_retire_leak.py +90 -0
- package/md_cg/test_p45_session_identity.py +250 -3
- package/md_cg/test_p6_consolidate.py +389 -7
- package/md_cg/test_sleep_gitlock.py +134 -0
- package/md_cg/test_spatial_coords3d.py +292 -0
- package/package.json +2 -2
- package/skills/plugin.json +1 -1
|
@@ -35,8 +35,11 @@ import shutil
|
|
|
35
35
|
import tempfile
|
|
36
36
|
|
|
37
37
|
from . import lifecycle, stg
|
|
38
|
+
from . import chain as _chain
|
|
39
|
+
from . import scrub as _scrub
|
|
38
40
|
from . import mcp_server
|
|
39
41
|
from . import tasks as _tasks
|
|
42
|
+
from .mdcg import MdCG
|
|
40
43
|
from .mdcos import MdCGOS
|
|
41
44
|
|
|
42
45
|
_root = tempfile.mkdtemp(prefix="mdcg_retire_")
|
|
@@ -135,6 +138,18 @@ def _idx(nid):
|
|
|
135
138
|
return (cg.index.get("nodes") or {}).get(nid) or {}
|
|
136
139
|
|
|
137
140
|
|
|
141
|
+
def _subgraph_children():
|
|
142
|
+
"""结构面正查表(`subgraph.children_index`:parent_id → [child_id...])。"""
|
|
143
|
+
from . import subgraph as _sg
|
|
144
|
+
return _sg.children_index(cg)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _subgraph_parents():
|
|
148
|
+
"""结构面反查表(`subgraph.parents_index`:child_id → [parent_id...])。"""
|
|
149
|
+
from . import subgraph as _sg
|
|
150
|
+
return _sg.parents_index(cg)
|
|
151
|
+
|
|
152
|
+
|
|
138
153
|
# ------------------------------------------------------------ R1 复现序列
|
|
139
154
|
n1 = cg.add("rl_v1", _body("旧事实 v1", "甲地(初始登记)"), layer="knowledge",
|
|
140
155
|
verification_basis="test", importance=0.4)
|
|
@@ -400,6 +415,81 @@ check("R4.6 op=session(action=recall)全链:archived 要点/未解问题
|
|
|
400
415
|
_sess_call_ids.isdisjoint({_note1, _unr1}) and _note2 in _sess_call_ids,
|
|
401
416
|
sorted(_sess_call_ids))
|
|
402
417
|
|
|
418
|
+
# ------------------------------------------- R5 基类 MdCG.search(2026-10-06 接线)
|
|
419
|
+
# 边界四条第 1 面(运维文档 §九.5 登记的已知未覆盖面):plain `MdCG` 实例
|
|
420
|
+
# (白箱 KB / 离线脚本 / 测试)的默认检索面此前未接判据——同序列下 MdCG 命中
|
|
421
|
+
# `['a_v1','a_v2']` 而 MdCGOS 只命中 `['a_v1']` 的取证。现按「默认读面必须消费
|
|
422
|
+
# 判据」接线;本腿:在役对照(converged 降权轴仍参与)→ archived 剔除。
|
|
423
|
+
def _base_ids():
|
|
424
|
+
r = MdCG(_root).search(Q, k=50, record=False)
|
|
425
|
+
res = r[0] if isinstance(r, tuple) else r
|
|
426
|
+
return {str(n.get("id")) for n, *_ in (res or [])}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
_b5 = _base_ids()
|
|
430
|
+
check("R5.1 基类面:archived(rl_v2)不出现、v1 出现",
|
|
431
|
+
n2 not in _b5 and n1 in _b5, sorted(_b5))
|
|
432
|
+
check("R5.2 基类面不误剔:converged(rl_converged)仍参与(降权轴)",
|
|
433
|
+
n3 in _b5, sorted(_b5))
|
|
434
|
+
|
|
435
|
+
# ------------------------------------------- R6 因果链面(2026-10-06 接线)
|
|
436
|
+
# 边界四条第 2 面:`chain.adjacency` 无 lifecycle 判据 ⇒ `cg(op=causal,
|
|
437
|
+
# action=chain)` 扩散进 archived(取证:链里出现归档 b_tgt)。接线后缺省
|
|
438
|
+
# `skip_archived=True` 剔除退役节点出边与指向退役目标的边;维护面
|
|
439
|
+
# (scrub 去污染抽查)显式 `skip_archived=False` 保留全量(面的判据分工)。
|
|
440
|
+
# 另钉一条本批实证的缓存缺口:归档发生在链**已建**之后(缓存已热)也必须
|
|
441
|
+
# 生效——`lifecycle.set_state` 现在补了 `chain.invalidate_cache` 单点。
|
|
442
|
+
_c6_seed = cg.add("rl_c6_seed", _body("因果链种子", "辛地"), layer="knowledge",
|
|
443
|
+
verification_basis="test", importance=0.4,
|
|
444
|
+
edges=[{"target": "rl_c6_tgt", "relation_type": "causal",
|
|
445
|
+
"confidence": 0.9}])
|
|
446
|
+
_c6_tgt = cg.add("rl_c6_tgt", _body("因果链目标(待退役)", "壬地"),
|
|
447
|
+
layer="knowledge", verification_basis="test", importance=0.4)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _c6_has(rep):
|
|
451
|
+
return any("rl_c6_tgt" in (c.get("nodes") or []) for c in (rep or []))
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
check("R6.0 前提:因果链在役时确实扩散到目标(非空断言,防本腿空转)",
|
|
455
|
+
_c6_has(cg.causal_chain("rl_c6_seed")), cg.causal_chain("rl_c6_seed"))
|
|
456
|
+
cg.set_state(_c6_tgt, "demoted", reason="守卫:链面降权", actor="test")
|
|
457
|
+
cg.set_state(_c6_tgt, "archived", reason="守卫:链面归档", actor="test")
|
|
458
|
+
check("R6.1 目标退役后因果链不扩散进它(缓存已热时亦生效——set_state 同点失效)",
|
|
459
|
+
not _c6_has(cg.causal_chain("rl_c6_seed")), cg.causal_chain("rl_c6_seed"))
|
|
460
|
+
check("R6.2 维护面旁路:adjacency(skip_archived=False) 仍含退役边(scrub 全量口径)",
|
|
461
|
+
any(t == "rl_c6_tgt" for t, _e in
|
|
462
|
+
(_chain.adjacency(cg, skip_archived=False).get("rl_c6_seed") or [])))
|
|
463
|
+
check("R6.3 scrub 面经旁路取数同证(维护写面不受退役过滤约束)",
|
|
464
|
+
any(t == "rl_c6_tgt" for t, _e in
|
|
465
|
+
(_scrub._adjacency(cg).get("rl_c6_seed") or [])))
|
|
466
|
+
cg.set_state(_c6_tgt, "active", reason="守卫:链面恢复", actor="test")
|
|
467
|
+
check("R6.4 恢复后因果链重新扩散(退役可逆,同面)",
|
|
468
|
+
_c6_has(cg.causal_chain("rl_c6_seed")), cg.causal_chain("rl_c6_seed"))
|
|
469
|
+
cg.set_state(_c6_tgt, "demoted", reason="守卫:复测降权", actor="test")
|
|
470
|
+
cg.set_state(_c6_tgt, "archived", reason="守卫:复测归档", actor="test")
|
|
471
|
+
|
|
472
|
+
# --------------------------------- R7 结构面(裁定:不接——定向面直读,钉住裁定)
|
|
473
|
+
# 边界四条第 3 面经使用者裁定(2026-10-06)**保持直读**:调用方给显式父/子
|
|
474
|
+
# id、返回 id 映射不回正文,属定向/结构面。本腿把裁定钉住——若未来有人
|
|
475
|
+
# "顺手接线",R7.1 转红即提醒该行为已被裁定(改行为须先改裁定)。
|
|
476
|
+
_c7_parent = cg.add("rl_c7_parent", _body("结构面父节点", "癸地"),
|
|
477
|
+
layer="knowledge", verification_basis="test", importance=0.4,
|
|
478
|
+
subgraph={"nodes": ["rl_c7_child"]})
|
|
479
|
+
_c7_child = cg.add("rl_c7_child", _body("结构面子节点(待退役)", "子地"),
|
|
480
|
+
layer="knowledge", verification_basis="test", importance=0.4)
|
|
481
|
+
check("R7.0 前提:children_index 含该父→子映射(非空断言,防本腿空转)",
|
|
482
|
+
"rl_c7_child" in (_subgraph_children().get("rl_c7_parent") or []),
|
|
483
|
+
_subgraph_children())
|
|
484
|
+
cg.set_state(_c7_child, "demoted", reason="守卫:结构面降权", actor="test")
|
|
485
|
+
cg.set_state(_c7_child, "archived", reason="守卫:结构面归档", actor="test")
|
|
486
|
+
check("R7.1 子节点退役后 children_index 映射仍在(裁定:结构面直读不接判据)",
|
|
487
|
+
"rl_c7_child" in (_subgraph_children().get("rl_c7_parent") or []),
|
|
488
|
+
_subgraph_children())
|
|
489
|
+
check("R7.2 parents_index 反向表同钉(裁定文本两侧对称——仅给一侧接线也会被本腿抓住)",
|
|
490
|
+
"rl_c7_parent" in (_subgraph_parents().get("rl_c7_child") or []),
|
|
491
|
+
_subgraph_parents())
|
|
492
|
+
|
|
403
493
|
print("=" * 58)
|
|
404
494
|
print("test_lifecycle_retire_leak: %d 通过 / %d 失败" % (_ok, len(_fail)))
|
|
405
495
|
if _fail:
|
|
@@ -17,16 +17,31 @@
|
|
|
17
17
|
E 会话要点:session_note 缺省用 Principal.session;recall 带回会话自我切片
|
|
18
18
|
F 审计归因:_audit.jsonl 条目带 harness/unit
|
|
19
19
|
G MCP 入口:_self_state_call 缺省会话透传 + index 快捷反查
|
|
20
|
+
R65 issue #65:session_recall ③ 近期事件段的**会话隔离**(外部报告 by ducc239,
|
|
21
|
+
对 v0.7.5 实测;本 workflow 修前复现)——假会话 id 空窗口 / A 只回 A /
|
|
22
|
+
B 只回 B / "*" 与 None 退回全局 / 缺 meta.session 者被丢弃 / compact 回归 /
|
|
23
|
+
MCP 入口端到端 / 文档口径在位;外加**定点变异自证**模式。
|
|
20
24
|
|
|
21
|
-
运行:python -m md_cg.test_p45_session_identity
|
|
25
|
+
运行:python -m md_cg.test_p45_session_identity # 正常跑
|
|
26
|
+
python -m md_cg.test_p45_session_identity --self-proof # 变异自证
|
|
27
|
+
退出码:0 全绿 | 1 断言失败 | 2 ANCHOR-MISS(锚点漂移,fail-closed)
|
|
28
|
+
|
|
29
|
+
变异自证的基线源 = **运行中的实现源码**(`inspect.getsource` 就地变异、就地复原),
|
|
30
|
+
**不读 git**——把基线绑到某个提交,下一次改动即失效(本仓已有两次教训)。
|
|
22
31
|
"""
|
|
23
32
|
from __future__ import annotations
|
|
24
33
|
|
|
34
|
+
import contextlib
|
|
35
|
+
import inspect
|
|
36
|
+
import io
|
|
25
37
|
import json
|
|
26
38
|
import os
|
|
39
|
+
import sys
|
|
27
40
|
import tempfile
|
|
41
|
+
import textwrap
|
|
28
42
|
|
|
29
43
|
from .mdcos import MdCGOS, MdCGSecure
|
|
44
|
+
from . import mdcos as _mdcos
|
|
30
45
|
from .security import Principal
|
|
31
46
|
from . import self_state as ss
|
|
32
47
|
from . import mcp_server as ms
|
|
@@ -65,6 +80,14 @@ def _restore(old):
|
|
|
65
80
|
|
|
66
81
|
|
|
67
82
|
def main():
|
|
83
|
+
global PASS, FAIL
|
|
84
|
+
if "--self-proof" in sys.argv:
|
|
85
|
+
return _r65_self_proof()
|
|
86
|
+
_rc = _r65_anchor_preflight() # 锚点漂移 → ANCHOR-MISS + 退出码 2
|
|
87
|
+
if _rc:
|
|
88
|
+
return _rc
|
|
89
|
+
PASS = FAIL = 0 # 计数器复位:变异自证模式内会复用本函数重跑
|
|
90
|
+
del FAILS[:]
|
|
68
91
|
# ---------- A. Principal 归因字段 ----------
|
|
69
92
|
print("\n[A] Principal 归因字段(与授权正交)")
|
|
70
93
|
p0 = Principal(actor="a0")
|
|
@@ -207,6 +230,10 @@ def main():
|
|
|
207
230
|
check("G3 refresh 经 MCP 缺省登记会话",
|
|
208
231
|
bool(out2.get("ok")))
|
|
209
232
|
|
|
233
|
+
# ---------- R65. 近期事件段的会话隔离(issue #65) ----------
|
|
234
|
+
print("\n[R65] session_recall ③ 近期事件:按会话隔离(issue #65;修前=全进程窗口)")
|
|
235
|
+
_r65_group()
|
|
236
|
+
|
|
210
237
|
print("\n" + "=" * 68)
|
|
211
238
|
print(f"通过 {PASS} / 失败 {FAIL}")
|
|
212
239
|
if FAILS:
|
|
@@ -215,6 +242,226 @@ def main():
|
|
|
215
242
|
return FAIL
|
|
216
243
|
|
|
217
244
|
|
|
245
|
+
# ======================================================================
|
|
246
|
+
# R65(issue #65):session_recall ③ 近期事件段的会话隔离
|
|
247
|
+
# ======================================================================
|
|
248
|
+
# 缺陷(外部报告 by ducc239,对 v0.7.5 实测;本 workflow 修前复现):③ 段取
|
|
249
|
+
# `self.recent_events(limit=…)` 时**不传会话**,而 `MdCGSecure.recent_events`
|
|
250
|
+
# 无 session 形参(只有 private/secret 档判会话归属,默认档全放行)⇒ recent 恒为
|
|
251
|
+
# 全进程窗口:多窗口/多会话并发时 A 的续接包里混入 B 的报文(宿主把它当用户输入
|
|
252
|
+
# 处理,可被压缩检查点记入——静默污染)。同族既有正确形态(旁证):`session_compact`
|
|
253
|
+
# 的 `if session: evs = [...]`、`_session_notes` 的 `session:{sid}` tag 过滤、
|
|
254
|
+
# MCP `_session_call` 已传 `session=`——缺口只在 session_recall ③ 段一处。
|
|
255
|
+
R65_A = "session-AAA-0000"
|
|
256
|
+
R65_B = "session-BBBB-0000"
|
|
257
|
+
R65_AX = R65_A + "-long" # A 的**前缀延伸**会话:钉「严格相等」判据
|
|
258
|
+
R65_FAKE = "session-zzzz-nope-0000"
|
|
259
|
+
R65_BARE = "N0 无会话归属的裸事件"
|
|
260
|
+
R65_GLOBAL = ("A1", "A2", "AX", "B1", "N0") # 全局窗口(不过滤)读数
|
|
261
|
+
|
|
262
|
+
# 五条事件:A×2 / B×1 / 裸×1(缺 meta.session)/ A 前缀延伸×1。
|
|
263
|
+
# 裸事件经**基类** remember_event 直写注入——安全档写入会把 meta.session
|
|
264
|
+
# setdefault 成本进程会话(mdcos.py:5087),测不到「缺 meta.session」形态;而读侧
|
|
265
|
+
# 对无归属事件的丢弃正是本 issue 的契约要求(存量数据 / 非安全档写入形态)。
|
|
266
|
+
R65_EVENTS = (
|
|
267
|
+
("user", "A1 会话A的第一句话", {"session": R65_A}),
|
|
268
|
+
("assistant", "A2 会话A的第二句话", {"session": R65_A}),
|
|
269
|
+
("user", "B1 会话B的第一句话", {"session": R65_B}),
|
|
270
|
+
("user", R65_BARE, {}),
|
|
271
|
+
("user", "AX 会话A前缀延伸会话", {"session": R65_AX}),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# 真源文件快照(导入时取一次):变异自证把 `MdCGOS.session_recall` 换成 exec 出来的
|
|
275
|
+
# 临时函数,那种函数**没有源文件**(`getsource` 会抛 OSError)——故注释行检查必须锚在
|
|
276
|
+
# 导入时记下的真源路径上;docstring 检查则走 `__doc__`(变异体也带,M6 才能转红)。
|
|
277
|
+
_R65_FILE = inspect.getsourcefile(MdCGOS.session_recall)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _r65_lib():
|
|
281
|
+
"""隔离库(系统临时目录合成库;不留仓内件)。"""
|
|
282
|
+
cg = MdCGOS(tempfile.mkdtemp(prefix="mdcg_p45_r65_"))
|
|
283
|
+
for role, text, meta in R65_EVENTS:
|
|
284
|
+
cg.remember_event(role, text, meta=meta)
|
|
285
|
+
return cg
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _r65_recent(cg, session):
|
|
289
|
+
"""续接包 recent 段的事件短标签(前 2 字),排序后返回(顺序不参与判据)。"""
|
|
290
|
+
pack = cg.session_recall(session=session, recent_limit=20,
|
|
291
|
+
budget_tokens=4000, include_state=False)
|
|
292
|
+
return sorted((r.get("text") or "")[:2] for r in (pack.get("recent") or []))
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _r65_cond_line():
|
|
296
|
+
"""`def session_recall` 上方紧邻的「生效条件:」注释行(本仓惯例:改函数须同步)。"""
|
|
297
|
+
lines = io.open(_R65_FILE, encoding="utf-8").read().splitlines()
|
|
298
|
+
for i, ln in enumerate(lines):
|
|
299
|
+
if ln.strip().startswith("def session_recall("):
|
|
300
|
+
j = i - 1
|
|
301
|
+
while j >= 0 and not lines[j].strip():
|
|
302
|
+
j -= 1
|
|
303
|
+
return lines[j] if j >= 0 else ""
|
|
304
|
+
return ""
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _r65_group():
|
|
308
|
+
"""R65 组断言(变异自证模式复用同一组,故不依赖模块级实例)。"""
|
|
309
|
+
cg = _r65_lib()
|
|
310
|
+
check("R65.1 假会话 id → recent 空窗口(修前=全库五条;杜绝错块)",
|
|
311
|
+
_r65_recent(cg, R65_FAKE) == [], _r65_recent(cg, R65_FAKE))
|
|
312
|
+
check("R65.2 会话 A → recent 恰为 A 的两条(严格相等:不含 B/裸事件/A 的前缀延伸会话)",
|
|
313
|
+
_r65_recent(cg, R65_A) == ["A1", "A2"], _r65_recent(cg, R65_A))
|
|
314
|
+
check("R65.3 会话 B → recent 恰为 B 的一条",
|
|
315
|
+
_r65_recent(cg, R65_B) == ["B1"], _r65_recent(cg, R65_B))
|
|
316
|
+
check('R65.4a session="*" → 不过滤(跨会话汇总合法用法)',
|
|
317
|
+
_r65_recent(cg, "*") == list(R65_GLOBAL), _r65_recent(cg, "*"))
|
|
318
|
+
check('R65.4b session=" * "(空白包裹)→ 仍按汇总处理(strip 判据)',
|
|
319
|
+
_r65_recent(cg, " * ") == list(R65_GLOBAL), _r65_recent(cg, " * "))
|
|
320
|
+
check("R65.5a session=None → 全局窗口(退回旧行为)",
|
|
321
|
+
_r65_recent(cg, None) == list(R65_GLOBAL), _r65_recent(cg, None))
|
|
322
|
+
check('R65.5b session=""(falsy)→ 全局窗口',
|
|
323
|
+
_r65_recent(cg, "") == list(R65_GLOBAL), _r65_recent(cg, ""))
|
|
324
|
+
check("R65.6a 指定会话下缺 meta.session 的事件被丢弃(不许漏;最坏空窗口)",
|
|
325
|
+
"N0" not in _r65_recent(cg, R65_A), _r65_recent(cg, R65_A))
|
|
326
|
+
check("R65.6b 不指定会话(None)时缺 meta.session 的事件照旧可见",
|
|
327
|
+
"N0" in _r65_recent(cg, None), _r65_recent(cg, None))
|
|
328
|
+
check("R65.7 回归:session_compact(session=A) 仍只压 A、compact(None) 照旧全局",
|
|
329
|
+
cg.session_compact(session=R65_A, limit=50)["events"] == 2
|
|
330
|
+
and cg.session_compact(session=None, limit=50)["events"] == len(R65_EVENTS),
|
|
331
|
+
(cg.session_compact(session=R65_A, limit=50)["events"],
|
|
332
|
+
cg.session_compact(session=None, limit=50)["events"]))
|
|
333
|
+
_doc = inspect.getdoc(MdCGOS.session_recall) or ""
|
|
334
|
+
check("R65.8 文档口径在位:docstring 声明「段跟会话走」=recent 跟会话(含『跨会话汇总』"
|
|
335
|
+
"例外口径)+ 紧邻「生效条件:」注释含过滤条款",
|
|
336
|
+
("段跟会话走" in _doc and "跨会话汇总" in _doc
|
|
337
|
+
and "recent 段按会话过滤" in _r65_cond_line()),
|
|
338
|
+
_r65_cond_line()[:48])
|
|
339
|
+
_mcp = MdCGSecure(tempfile.mkdtemp(prefix="mdcg_p45_r65_mcp_"),
|
|
340
|
+
principal=Principal(actor="r65_mcp", session=R65_A))
|
|
341
|
+
_mcp.remember_event("user", "A1 生产形态:本会话事件", meta={"session": R65_A})
|
|
342
|
+
_mcp.remember_event("user", "B1 生产形态:他会话事件", meta={"session": R65_B})
|
|
343
|
+
_hit = ms._session_call(_mcp, {"action": "recall", "session": R65_A,
|
|
344
|
+
"recent_limit": 20, "budget_tokens": 4000,
|
|
345
|
+
"include_state": False})
|
|
346
|
+
_none = ms._session_call(_mcp, {"action": "recall", "session": R65_FAKE,
|
|
347
|
+
"recent_limit": 20, "budget_tokens": 4000,
|
|
348
|
+
"include_state": False})
|
|
349
|
+
check("R65.9 MCP 入口(_session_call recall)端到端:本会话只回本会话、假 id 空窗口",
|
|
350
|
+
sorted((r.get("text") or "")[:2] for r in _hit.get("recent") or []) == ["A1"]
|
|
351
|
+
and (_none.get("recent") or []) == [],
|
|
352
|
+
(_hit.get("recent"), _none.get("recent")))
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# ---------------------------------------------------------------- 定点变异自证
|
|
356
|
+
# 锚点在**去缩进后的实现源码**上定位(方法体在类体内缩进 4 格,编译前统一 dedent)。
|
|
357
|
+
# 每条变异声明**期望转红的断言项**;实跑红项与期望必须**恰好相等**——多红=断言语义
|
|
358
|
+
# 纠缠,少红=该判据空转(本仓教训:test_neg_condition_hits 的「整条命中档」曾 31 条
|
|
359
|
+
# 断言全绿而分支已删)。锚点漂移(命中次数 ≠1)→ ANCHOR-MISS + 退出码 2(fail-closed)。
|
|
360
|
+
_R65_A_BLOCK = (
|
|
361
|
+
' if session and str(session).strip() != "*":\n'
|
|
362
|
+
' evs = [r for r in evs if (r.get("meta") or {}).get("session") == session]\n')
|
|
363
|
+
_R65_A_COND = 'if session and str(session).strip() != "*":'
|
|
364
|
+
_R65_A_STRIP = 'str(session).strip() != "*"'
|
|
365
|
+
_R65_A_FILTER = ('evs = [r for r in evs '
|
|
366
|
+
'if (r.get("meta") or {}).get("session") == session]')
|
|
367
|
+
_R65_A_DOC = "段跟会话走"
|
|
368
|
+
_R65_ANCHORS = (_R65_A_BLOCK, _R65_A_COND, _R65_A_STRIP, _R65_A_FILTER, _R65_A_DOC)
|
|
369
|
+
|
|
370
|
+
_R65_MUTATIONS = (
|
|
371
|
+
("①删整段会话过滤(recent 回到全进程窗口)", _R65_A_BLOCK, "",
|
|
372
|
+
{"R65.1", "R65.2", "R65.3", "R65.6a", "R65.9"}),
|
|
373
|
+
("②删 \"*\" 例外(星号也过滤)", _R65_A_COND, "if session:",
|
|
374
|
+
{"R65.4a", "R65.4b"}),
|
|
375
|
+
("③去 strip(\" * \" 不再按汇总处理)", _R65_A_STRIP, 'str(session) != "*"',
|
|
376
|
+
{"R65.4b"}),
|
|
377
|
+
("④缺 meta.session 改 fail-open(放行)", _R65_A_FILTER,
|
|
378
|
+
('evs = [r for r in evs if not (r.get("meta") or {}).get("session") '
|
|
379
|
+
'or (r.get("meta") or {}).get("session") == session]'),
|
|
380
|
+
# 签名:放行无归属事件 ⇒ 三个「按会话」腿任一都混入裸事件(R65.1/2/3 与 R65.6a
|
|
381
|
+
# 同红);R65.9 的库内无裸事件,故它**不**红——与变异① 的签名差恰在这一项。
|
|
382
|
+
{"R65.1", "R65.2", "R65.3", "R65.6a"}),
|
|
383
|
+
("⑤去 falsy 守卫(None/\"\" 也过滤)", _R65_A_COND,
|
|
384
|
+
'if str(session).strip() != "*":', {"R65.5a", "R65.5b"}),
|
|
385
|
+
("⑥文档口径回退(docstring 反转)", _R65_A_DOC, "段不跟会话走", {"R65.8"}),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _r65_anchor_preflight():
|
|
390
|
+
"""锚点自检:任一锚点在实现源码里命中次数 ≠1 → ANCHOR-MISS + 退出码 2(fail-closed)。"""
|
|
391
|
+
src = textwrap.dedent(inspect.getsource(MdCGOS.session_recall))
|
|
392
|
+
bad = [(a, src.count(a)) for a in _R65_ANCHORS if src.count(a) != 1]
|
|
393
|
+
if not bad:
|
|
394
|
+
return 0
|
|
395
|
+
for a, n in bad:
|
|
396
|
+
print(" ANCHOR-MISS 锚点漂移(命中 %d 次,期望恰好 1):%r" % (n, a[:70]))
|
|
397
|
+
print(" => 实现已漂移,变异表失效:退出码 2(fail-closed;请同步 _R65_MUTATIONS 锚点)")
|
|
398
|
+
return 2
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _r65_run():
|
|
402
|
+
"""跑 R65 组,返回(转红断言项集合, 通过数, 失败数)。"""
|
|
403
|
+
global PASS, FAIL
|
|
404
|
+
PASS = FAIL = 0
|
|
405
|
+
del FAILS[:]
|
|
406
|
+
_r65_group()
|
|
407
|
+
return ({n.split(" ", 1)[0] for n in FAILS}, PASS, FAIL)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _r65_self_proof():
|
|
411
|
+
"""逐条变异 → 要求恰好命中期望红项;复原后重跑全套应当全绿。"""
|
|
412
|
+
rc = _r65_anchor_preflight()
|
|
413
|
+
if rc:
|
|
414
|
+
return rc
|
|
415
|
+
print("!! 定点变异自证:就地变异运行中的实现源码(不读 git),逐条要求**恰好**命中期望红项\n")
|
|
416
|
+
src = textwrap.dedent(inspect.getsource(MdCGOS.session_recall))
|
|
417
|
+
orig = MdCGOS.session_recall
|
|
418
|
+
bad = []
|
|
419
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
420
|
+
base_red, _, _ = _r65_run()
|
|
421
|
+
print(" 未变异基线:红项 %d %s" % (len(base_red),
|
|
422
|
+
"(应为 0)" if base_red else ""))
|
|
423
|
+
if base_red:
|
|
424
|
+
bad.append("未变异基线即转红:%s" % sorted(base_red))
|
|
425
|
+
marks = "①②③④⑤⑥"
|
|
426
|
+
for i, (name, old, new, expect) in enumerate(_R65_MUTATIONS):
|
|
427
|
+
ns = dict(vars(_mdcos))
|
|
428
|
+
exec(compile(src.replace(old, new), "<r65-mutated-%d>" % i, "exec"), ns)
|
|
429
|
+
MdCGOS.session_recall = ns["session_recall"]
|
|
430
|
+
try:
|
|
431
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
432
|
+
red, _, _ = _r65_run()
|
|
433
|
+
except Exception as exc: # noqa: BLE001
|
|
434
|
+
red = {"<变异体运行异常:%s>" % type(exc).__name__}
|
|
435
|
+
finally:
|
|
436
|
+
MdCGOS.session_recall = orig
|
|
437
|
+
hit = red == expect
|
|
438
|
+
if not hit:
|
|
439
|
+
bad.append("变异%s %s:红项 %s ≠ 期望 %s"
|
|
440
|
+
% (marks[i], name, sorted(red), sorted(expect)))
|
|
441
|
+
print(" 变异%s %-32s 红项 %d(期望 %d)%s"
|
|
442
|
+
% (marks[i], name, len(red), len(expect),
|
|
443
|
+
"PASS" if hit else "**FAIL** 实=%s 期=%s" % (sorted(red), sorted(expect))))
|
|
444
|
+
argv_bak = list(sys.argv)
|
|
445
|
+
sys.argv[:] = [a for a in argv_bak if a != "--self-proof"]
|
|
446
|
+
_buf = io.StringIO()
|
|
447
|
+
try:
|
|
448
|
+
with contextlib.redirect_stdout(_buf):
|
|
449
|
+
full_rc = main()
|
|
450
|
+
finally:
|
|
451
|
+
sys.argv[:] = argv_bak
|
|
452
|
+
print(" 复原后重跑全套(A–G + R65):退出码 %d %s"
|
|
453
|
+
% (full_rc, "(全绿)" if full_rc == 0 else "**非全绿**"))
|
|
454
|
+
if full_rc:
|
|
455
|
+
for _ln in _buf.getvalue().splitlines():
|
|
456
|
+
if "[FAIL]" in _ln:
|
|
457
|
+
print(" 复原重跑红:%s" % _ln.strip()[:120])
|
|
458
|
+
bad.append("复原后全套非全绿(rc=%d)" % full_rc)
|
|
459
|
+
print("\n变异自证:%s"
|
|
460
|
+
% ("PASS(六条腿逐条恰好命中期望红项;复原后全绿)" if not bad
|
|
461
|
+
else "FAIL —— " + ";".join(bad)))
|
|
462
|
+
return 0 if not bad else 1
|
|
463
|
+
|
|
464
|
+
|
|
218
465
|
if __name__ == "__main__":
|
|
219
|
-
|
|
220
|
-
sys.exit(
|
|
466
|
+
_rc = main()
|
|
467
|
+
sys.exit(_rc if _rc in (0, 2) else 1)
|