@furongjun1999/dsh-memory 0.5.0 → 0.5.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 +552 -465
- package/docs/README.md +1 -0
- package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +19 -3
- package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
- 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 +40 -40
- package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- package/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/index.js +7 -1
- package/lib/lib/roleplay_web.js +530 -443
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/audit.py +12 -1
- package/md_cg/backfill.py +16 -15
- package/md_cg/backfill_bucket_zh.py +35 -0
- package/md_cg/bench_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- package/md_cg/branches.py +18 -2
- package/md_cg/ccgc.py +3 -2
- package/md_cg/chain.py +19 -4
- package/md_cg/consolidate.py +7 -6
- package/md_cg/crosscheck.py +4 -3
- package/md_cg/crypto.py +439 -437
- package/md_cg/datapath.py +395 -335
- package/md_cg/evidence.py +582 -580
- package/md_cg/export.py +3 -1
- package/md_cg/forgetting.py +2 -2
- package/md_cg/fsutil.py +48 -0
- package/md_cg/hotcache.py +255 -238
- package/md_cg/interop.py +161 -22
- package/md_cg/judgment_manifest.py +177 -0
- package/md_cg/links.py +655 -622
- package/md_cg/mcp_server.py +280 -29
- package/md_cg/mdcg.py +616 -125
- package/md_cg/mdcos.py +430 -66
- package/md_cg/mreview/govern.py +5 -4
- package/md_cg/postings.py +4 -2
- package/md_cg/readcache.py +76 -18
- package/md_cg/reconcile.py +228 -0
- package/md_cg/review_cli.py +170 -0
- package/md_cg/routing.py +28 -0
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +862 -852
- package/md_cg/security.py +385 -275
- package/md_cg/selfreport.py +3 -2
- package/md_cg/signer.py +565 -562
- package/md_cg/sources.py +3 -2
- package/md_cg/stg.py +6 -0
- package/md_cg/sustain.py +1168 -1138
- package/md_cg/test_access_hints.py +147 -0
- package/md_cg/test_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +259 -249
- package/md_cg/test_chain_read_isolate.py +168 -0
- package/md_cg/test_datapath_device_name.py +203 -0
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +186 -166
- package/md_cg/test_govern_directread.py +421 -0
- package/md_cg/test_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +228 -147
- package/md_cg/test_index_durability.py +238 -224
- package/md_cg/test_interop.py +4 -2
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_links_concurrent_write.py +188 -0
- package/md_cg/test_merge_upsert.py +168 -0
- package/md_cg/test_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- package/md_cg/test_p1x_ref_root.py +160 -0
- package/md_cg/test_p27_docindex.py +774 -765
- package/md_cg/test_p2_mcp.py +3 -0
- package/md_cg/test_p32_backfill.py +304 -298
- package/md_cg/test_p39_verify_flow.py +90 -50
- package/md_cg/test_p47_session_view.py +60 -25
- package/md_cg/test_propose_tail_index.py +157 -0
- package/md_cg/test_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +55 -7
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_s1.py +6 -2
- package/md_cg/test_retr_s1b.py +67 -0
- package/md_cg/test_retr_s7.py +8 -0
- package/md_cg/test_retr_s9_entity_ctx.py +181 -175
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_semantic_canonical.py +255 -241
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- package/md_cg/test_v14_fixes.py +415 -397
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- package/md_cg/theory.py +276 -273
- package/md_cg/tokens.py +734 -677
- package/md_cg/units.py +3 -2
- package/md_cg/vision_evidence.py +4 -3
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
- package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
- package/md_cg/writepipe.py +554 -550
- package/package.json +6 -2
- package/src/bridge.ts +434 -401
- package/src/index.ts +526 -518
- package/src/lib/roleplay_web.ts +1019 -932
- package/src/lib/token_store.ts +202 -192
|
@@ -43,63 +43,103 @@ def main():
|
|
|
43
43
|
|
|
44
44
|
# 派发原语:spec 落验证实例 jobs 目录(非阻塞)
|
|
45
45
|
vjobs = os.path.join(tmp, "vjobs")
|
|
46
|
-
rep = dispatch_verify_job(vjobs, iter_id, subject_fingerprint="
|
|
46
|
+
rep = dispatch_verify_job(vjobs, iter_id, subject_fingerprint="e" * 64)
|
|
47
47
|
check("1b 派发写 spec+status 且非阻塞返回",
|
|
48
48
|
rep.get("ok") is True
|
|
49
49
|
and os.path.isfile(os.path.join(rep["dispatched"], "spec.json")))
|
|
50
50
|
|
|
51
51
|
# runner 冒烟:env 注入轻量探针命令 + 身份/冻结/subject_fp
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
52
|
+
# hive/ 是**源码树子系统**(出货包 files 不含它)→ 依赖 runner 的 2a~3c
|
|
53
|
+
# 如实 SKIP,并改为钉住缺件时的**新契约**(2026-09-24 修复):
|
|
54
|
+
# 派发必须透出 warning,冻结凭证必须记录缺失判据面组。
|
|
55
|
+
hive_runner = os.path.join(repo, "hive", "verify_runner.py")
|
|
56
|
+
if not os.path.isfile(hive_runner):
|
|
57
|
+
check("2* 缺 hive runner 时派发透出 warning(不假装能跑)",
|
|
58
|
+
bool(rep.get("warning")), str(rep.get("warning"))[:160])
|
|
59
|
+
_fr = json.load(open(os.path.join(repo, "hive", "interop", iter_id,
|
|
60
|
+
"frozen.json"), encoding="utf-8"))
|
|
61
|
+
check("4* 冻结凭证记录缺失判据面组(不静默少算)",
|
|
62
|
+
isinstance(_fr.get("missing_patterns"), list)
|
|
63
|
+
and bool(_fr["missing_patterns"]),
|
|
64
|
+
str(_fr.get("missing_patterns")))
|
|
65
|
+
print(" SKIP 2a~3c(需要 hive/verify_runner.py,源码树子系统)")
|
|
66
|
+
else:
|
|
67
|
+
env = dict(os.environ)
|
|
68
|
+
env.update({
|
|
69
|
+
"HIVE_INSTANCE": "verifier", "HIVE_ROLE": "verifier",
|
|
70
|
+
"SUBJECT_FP": "e" * 64, "ITER_ID": iter_id,
|
|
71
|
+
})
|
|
72
|
+
# freeze 的 digest 基于真判据面——runner 内 A3 用同一 digest,须一致:
|
|
73
|
+
# runner 在本 repo 内跑,判据面未变 → A3 成立
|
|
74
|
+
r = subprocess.run([sys.executable, hive_runner,
|
|
75
|
+
iter_id, "--smoke"], env=env, capture_output=True,
|
|
76
|
+
text=True, encoding="utf-8", errors="replace",
|
|
77
|
+
cwd=tmp) # cwd≠repo:验证 runner 以自身位置锚定
|
|
78
|
+
out = json.loads(r.stdout)
|
|
79
|
+
check("2a runner 产出 verdict(ok 结构)",
|
|
80
|
+
out.get("ok") is True and "valid" in out, r.stdout[:160])
|
|
81
|
+
vp = os.path.join(repo, "hive", "interop", iter_id, "verdict.json")
|
|
82
|
+
v = json.load(open(vp, encoding="utf-8"))
|
|
83
|
+
check("2b 三断言全过 → valid=true 且 verdict=pass",
|
|
84
|
+
v.get("valid") is True and v.get("verdict") == "pass",
|
|
85
|
+
str(v.get("details"))[:160])
|
|
86
|
+
check("2c 套件计数经宽松解析累加(3+4=7)",
|
|
87
|
+
v.get("passed") == 7, f"passed={v.get('passed')}")
|
|
74
88
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
f"rc={r2.returncode} out={r2.stdout[:120]} err={r2.stderr[:120]}")
|
|
89
|
+
# 角色守卫:非 verifier 拒跑
|
|
90
|
+
env2 = dict(env)
|
|
91
|
+
env2["HIVE_ROLE"] = "primary"
|
|
92
|
+
r2 = subprocess.run([sys.executable, hive_runner, iter_id],
|
|
93
|
+
env=env2, capture_output=True,
|
|
94
|
+
text=True, encoding="utf-8", errors="replace")
|
|
95
|
+
check("2d 角色守卫:primary 拒跑互验(§7.1)",
|
|
96
|
+
r2.returncode == 3 and "角色守卫" in r2.stdout,
|
|
97
|
+
f"rc={r2.returncode} out={r2.stdout[:120]} err={r2.stderr[:120]}")
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
# iter_id 白名单(v2 N5/N11,2026-09-25):CLI 直跑 + 自设
|
|
100
|
+
# HIVE_ROLE=verifier 骗过角色守卫(它只查 env 值)时,穿越形态
|
|
101
|
+
# 必须拒跑且不触盘——fail-closed 在任何路径拼接之前,与姊妹入口
|
|
102
|
+
# write_verdict_to_repo(interop.py:316)同模板统一防御。
|
|
103
|
+
probe = f"evil_probe_n5_{os.getpid()}"
|
|
104
|
+
evil_rel = os.path.join("..", "..", "..", probe)
|
|
105
|
+
# REPO/hive/interop/<evil_rel>/frozen.json 三级上跳 → REPO 父目录
|
|
106
|
+
tgt = os.path.join(os.path.dirname(repo), probe)
|
|
107
|
+
r3 = subprocess.run([sys.executable, hive_runner, evil_rel,
|
|
108
|
+
"--smoke"], env=env, capture_output=True,
|
|
109
|
+
text=True, encoding="utf-8", errors="replace")
|
|
110
|
+
check("2e iter_id 穿越形态拒跑且不触盘(N5/N11)",
|
|
111
|
+
r3.returncode == 2 and "iter_id 非法" in r3.stdout
|
|
112
|
+
and not os.path.exists(tgt),
|
|
113
|
+
f"rc={r3.returncode} out={r3.stdout[:120]} "
|
|
114
|
+
f"tgt_exists={os.path.exists(tgt)}")
|
|
115
|
+
evil_abs = os.path.join(tempfile.gettempdir(),
|
|
116
|
+
f"evil_abs_n5_{os.getpid()}")
|
|
117
|
+
r4 = subprocess.run([sys.executable, hive_runner, evil_abs,
|
|
118
|
+
"--smoke"], env=env, capture_output=True,
|
|
119
|
+
text=True, encoding="utf-8", errors="replace")
|
|
120
|
+
check("2f iter_id 绝对路径注入拒跑且不触盘",
|
|
121
|
+
r4.returncode == 2 and "iter_id 非法" in r4.stdout
|
|
122
|
+
and not os.path.exists(evil_abs),
|
|
123
|
+
f"rc={r4.returncode} out={r4.stdout[:120]} "
|
|
124
|
+
f"abs_exists={os.path.exists(evil_abs)}")
|
|
125
|
+
|
|
126
|
+
# 脱敏纵深 + 入库(不 commit)
|
|
127
|
+
bad = dict(v)
|
|
128
|
+
bad["details"] = [{"leak": r"E:\private\node.log"}]
|
|
129
|
+
try:
|
|
130
|
+
sanity_check_verdict(bad)
|
|
131
|
+
check("3a 落盘前脱敏纵深拦截", False, "违规未拦")
|
|
132
|
+
except Exception:
|
|
133
|
+
check("3a 落盘前脱敏纵深拦截", True)
|
|
134
|
+
w = write_verdict_to_repo(v, repo=repo, do_commit=False)
|
|
135
|
+
check("3b verdict 入受版本控制目录(hive/interop/<iter>/)",
|
|
136
|
+
w.get("ok") is True
|
|
137
|
+
and "hive" in w["path"] and "interop" in w["path"], w.get("path"))
|
|
138
|
+
try:
|
|
139
|
+
write_verdict_to_repo({**v, "iter_id": "../evil"}, repo=repo)
|
|
140
|
+
check("3c iter_id 路径注入拒绝", False, "未抛")
|
|
141
|
+
except ValueError:
|
|
142
|
+
check("3c iter_id 路径注入拒绝", True)
|
|
103
143
|
finally:
|
|
104
144
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
105
145
|
shutil.rmtree(os.path.join(repo, "hive", "interop", iter_id),
|
|
@@ -84,9 +84,12 @@ def _restore(old):
|
|
|
84
84
|
os.environ[k] = v
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def _p(actor, session):
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
def _p(actor, session, clearance=None, can_admin=True):
|
|
88
|
+
# 会话隔离用例(D/E 段)需非 admin 身份(admin 对绑定档豁免);
|
|
89
|
+
# 其余段维持原口径(designer + 默认密级 + admin)。
|
|
90
|
+
return Principal(actor=actor,
|
|
91
|
+
clearance=clearance or DEFAULT_SENSITIVITY,
|
|
92
|
+
can_write=True, can_admin=can_admin, role="designer",
|
|
90
93
|
session=session, harness="test-harness")
|
|
91
94
|
|
|
92
95
|
|
|
@@ -101,10 +104,16 @@ def _ids(tl):
|
|
|
101
104
|
|
|
102
105
|
def _mk(root):
|
|
103
106
|
"""两个会话各写一条(带 temporal → timeline 可见)。"""
|
|
104
|
-
cga = MdCGSecure(root, principal=_p("alice", "sess_A"
|
|
107
|
+
cga = MdCGSecure(root, principal=_p("alice", "sess_A",
|
|
108
|
+
clearance="private", can_admin=False))
|
|
105
109
|
cga.add("n_a", "阿尔法 属于 会话A 的知识点", layer="knowledge", temporal=1000.0)
|
|
106
|
-
|
|
110
|
+
cga.add("n_pa", "贝塔 会话A 的私有知识点", layer="knowledge",
|
|
111
|
+
sensitivity="private")
|
|
112
|
+
cgb = MdCGSecure(root, principal=_p("bob", "sess_B",
|
|
113
|
+
clearance="private", can_admin=False))
|
|
107
114
|
cgb.add("n_b", "阿尔法 属于 会话B 的知识点", layer="knowledge", temporal=2000.0)
|
|
115
|
+
cgb.add("n_pb", "贝塔 会话B 的私有知识点", layer="knowledge",
|
|
116
|
+
sensitivity="private")
|
|
108
117
|
return cgb
|
|
109
118
|
|
|
110
119
|
|
|
@@ -119,7 +128,8 @@ def test_ab(cgb):
|
|
|
119
128
|
|
|
120
129
|
print("\n[B] timeline 会话视图三态")
|
|
121
130
|
all_tl = stg.timeline(cgb, limit=100)
|
|
122
|
-
check("B1
|
|
131
|
+
check("B1 缺省读遍所有会话(可见面=共享档+本会话绑定档)",
|
|
132
|
+
all_tl["count"] == 3, all_tl["count"])
|
|
123
133
|
check("B2 缺省返回体 session=None(跨会话视图)",
|
|
124
134
|
all_tl["session"] is None, all_tl["session"])
|
|
125
135
|
check("B3 items 逐条回带会话归属(看遍≠丢归属)",
|
|
@@ -135,11 +145,13 @@ def test_ab(cgb):
|
|
|
135
145
|
and cross_tl["session"] is None, cross_tl["session"])
|
|
136
146
|
|
|
137
147
|
a_tl = stg.timeline(cgb, limit=100, session="sess_A")
|
|
138
|
-
check("B6
|
|
148
|
+
check("B6 具体值只取本会话(他人 private 经授权面剔除)",
|
|
149
|
+
_ids(a_tl) == ["n_a"], _ids(a_tl))
|
|
139
150
|
check("B7 返回体 session=生效值(非跨会话)",
|
|
140
151
|
a_tl["session"] == "sess_A", a_tl["session"])
|
|
141
152
|
b_tl = stg.timeline(cgb, limit=100, session="sess_B")
|
|
142
|
-
check("B8
|
|
153
|
+
check("B8 另一会话只取它的节点(含其绑定档)",
|
|
154
|
+
set(_ids(b_tl)) == {"n_b", "n_pb"}, _ids(b_tl))
|
|
143
155
|
|
|
144
156
|
z_tl = stg.timeline(cgb, limit=100, session="sess_Z")
|
|
145
157
|
check("B9 未知会话为空(不因缺省而放行)", z_tl["count"] == 0, z_tl["count"])
|
|
@@ -154,15 +166,27 @@ def test_c(cgb):
|
|
|
154
166
|
a_tl = stg.timeline(cgb, limit=100, session="sess_A")
|
|
155
167
|
check("C3 rebuild 后按会话过滤不静默全空", _ids(a_tl) == ["n_a"], _ids(a_tl))
|
|
156
168
|
all_tl = stg.timeline(cgb, limit=100)
|
|
157
|
-
check("C4 rebuild
|
|
169
|
+
check("C4 rebuild 后跨会话仍读全(可见面 3)",
|
|
170
|
+
all_tl["count"] == 3, all_tl["count"])
|
|
158
171
|
|
|
159
172
|
|
|
160
173
|
def test_d(cgb):
|
|
161
|
-
print("\n[D] 候选层会话过滤(search / recall
|
|
174
|
+
print("\n[D] 候选层会话过滤(search / recall)——issue #35 定稿:分档判定")
|
|
175
|
+
# 视角=bob(sess_B)。共享档(internal 默认)跨会话可见:传不传 session
|
|
176
|
+
# 都能看到 alice 的共享节点;绑定档(private)按身份(principal.session)
|
|
177
|
+
# 判定,查询参数自报的会话不构成豁免。
|
|
162
178
|
res, _ = cgb.search("阿尔法", session="sess_A", judge=False, record=False)
|
|
163
179
|
ids = {_row(r).get("id") for r in res}
|
|
164
|
-
check("
|
|
165
|
-
"n_a"
|
|
180
|
+
check("D1a 共享档(internal 默认)跨会话可见",
|
|
181
|
+
{"n_a", "n_b"} <= ids, ids)
|
|
182
|
+
res_p, _ = cgb.search("贝塔", judge=False, record=False)
|
|
183
|
+
pids = {_row(r).get("id") for r in res_p}
|
|
184
|
+
check("D1b 绑定档(private)仅归属会话(bob 只见自己的)",
|
|
185
|
+
"n_pb" in pids and "n_pa" not in pids, pids)
|
|
186
|
+
res_h, _ = cgb.search("贝塔", session="sess_A", judge=False, record=False)
|
|
187
|
+
hids = {_row(r).get("id") for r in res_h}
|
|
188
|
+
check("D1c 查询参数自报他人会话不越权(仍只见自己的 private)",
|
|
189
|
+
"n_pb" in hids and "n_pa" not in hids, hids)
|
|
166
190
|
|
|
167
191
|
res_x, _ = cgb.search("阿尔法", session="*", judge=False, record=False)
|
|
168
192
|
ids_x = {_row(r).get("id") for r in res_x}
|
|
@@ -174,8 +198,12 @@ def test_d(cgb):
|
|
|
174
198
|
|
|
175
199
|
pack = cgb.recall("阿尔法", session="sess_A")
|
|
176
200
|
hid = {_row(h).get("id") for h in (pack.get("pack") or [])}
|
|
177
|
-
check("
|
|
178
|
-
"n_a"
|
|
201
|
+
check("D4a recall(RRF 主链)共享档跨会话",
|
|
202
|
+
{"n_a", "n_b"} <= hid, hid)
|
|
203
|
+
pack_p = cgb.recall("贝塔")
|
|
204
|
+
hid_p = {_row(h).get("id") for h in (pack_p.get("pack") or [])}
|
|
205
|
+
check("D4b recall 绑定档仅归属会话",
|
|
206
|
+
"n_pb" in hid_p and "n_pa" not in hid_p, hid_p)
|
|
179
207
|
|
|
180
208
|
pack_x = cgb.recall("阿尔法", session="*")
|
|
181
209
|
hid_x = {_row(h).get("id") for h in (pack_x.get("pack") or [])}
|
|
@@ -183,24 +211,29 @@ def test_d(cgb):
|
|
|
183
211
|
|
|
184
212
|
|
|
185
213
|
def test_e(cgb):
|
|
186
|
-
print("\n[E]
|
|
187
|
-
# 图扩散会绕过 _candidates
|
|
188
|
-
# 验证 MdCGSecure.search_rrf
|
|
214
|
+
print("\n[E] 图扩展二次过滤(跨会话串台守卫)——分档口径")
|
|
215
|
+
# 图扩散会绕过 _candidates:直接把父类 RRF 结果替换成「两共享 + 两绑定」,
|
|
216
|
+
# 验证 MdCGSecure.search_rrf 的二次过滤按档判定——视角 bob:共享档
|
|
217
|
+
# (n_a/n_b)互可见,绑定档仅自己的 n_pb 能回来;session 参数三态
|
|
218
|
+
# (他人会话 / "*" / 缺省)不影响绑定档结果(身份不可自报)。
|
|
189
219
|
crafted = [({"id": "n_a"}, 1.0, None, None),
|
|
190
|
-
({"id": "n_b"}, 0.9, None, None)
|
|
220
|
+
({"id": "n_b"}, 0.9, None, None),
|
|
221
|
+
({"id": "n_pa"}, 0.8, None, None),
|
|
222
|
+
({"id": "n_pb"}, 0.7, None, None)]
|
|
191
223
|
orig = MdCGOS.search_rrf
|
|
192
224
|
MdCGOS.search_rrf = lambda self, *a, **kw: (list(crafted), {"tier": "RRF"})
|
|
193
225
|
try:
|
|
194
226
|
kept, _ = cgb.search_rrf("q", session="sess_A")
|
|
195
227
|
ids = [r[0]["id"] for r in kept]
|
|
196
|
-
check("E1
|
|
228
|
+
check("E1 绑定档按身份:他人 private 不得顺边回来(共享档保留)",
|
|
229
|
+
{"n_a", "n_b", "n_pb"} <= set(ids) and "n_pa" not in ids, ids)
|
|
197
230
|
kept_x, _ = cgb.search_rrf("q", session="*")
|
|
198
|
-
check("E2 \"*\"
|
|
199
|
-
{r[0]["id"] for r in kept_x} == {"n_a", "n_b"},
|
|
231
|
+
check("E2 \"*\" 同口径(绑定档不受查询参数影响)",
|
|
232
|
+
{r[0]["id"] for r in kept_x} == {"n_a", "n_b", "n_pb"},
|
|
200
233
|
[r[0]["id"] for r in kept_x])
|
|
201
234
|
kept_all, _ = cgb.search_rrf("q")
|
|
202
|
-
check("E3
|
|
203
|
-
{r[0]["id"] for r in kept_all} == {"n_a", "n_b"},
|
|
235
|
+
check("E3 缺省同口径(兼容)",
|
|
236
|
+
{r[0]["id"] for r in kept_all} == {"n_a", "n_b", "n_pb"},
|
|
204
237
|
[r[0]["id"] for r in kept_all])
|
|
205
238
|
finally:
|
|
206
239
|
MdCGOS.search_rrf = orig
|
|
@@ -251,10 +284,12 @@ def test_g(cgb):
|
|
|
251
284
|
|
|
252
285
|
cross = ms.call_tool(cgb, "stg",
|
|
253
286
|
{"op": "timeline", "limit": 100, "session": "*"})
|
|
254
|
-
check("G4 MCP 面 \"*\"
|
|
287
|
+
check("G4 MCP 面 \"*\" 跨会话读全(可见面 3)",
|
|
288
|
+
cross.get("count") == 3, cross.get("count"))
|
|
255
289
|
|
|
256
290
|
default = ms.call_tool(cgb, "stg", {"op": "timeline", "limit": 100})
|
|
257
|
-
check("G5
|
|
291
|
+
check("G5 缺省读全(兼容旧调用,可见面 3)",
|
|
292
|
+
default.get("count") == 3, default.get("count"))
|
|
258
293
|
|
|
259
294
|
|
|
260
295
|
def main():
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""propose 尾部增量对账守卫(issue:propose 每条全量重读 inbox+decisions)。
|
|
4
|
+
|
|
5
|
+
缺陷形态(缺陷报告实测):propose 每条在 strict 排它锁内
|
|
6
|
+
read_jsonl(inbox_log) + read_jsonl(decisions_log) 全量逐条 json.loads
|
|
7
|
+
(O(M+D)/条,批量累计 O(M²)),存量无 payload_hash 还逐条现算 sha1。
|
|
8
|
+
万条存量单条 ~26ms、批量 O(M²) 分钟级。
|
|
9
|
+
|
|
10
|
+
本守卫断言(调用计数/复杂度形态,非时间阈值):
|
|
11
|
+
T1 二次 propose 零全量读:同实例第 2 次 propose(无外部写)期间
|
|
12
|
+
mdcos.read_jsonl 对 inbox/decisions 的调用数为 0(旧代码每条各 1 次
|
|
13
|
+
全量 → 红);
|
|
14
|
+
T2 批量 O(M):500 条连续 propose 全程,首条装载后 read_jsonl 调用数为 0
|
|
15
|
+
(旧代码 2×499 次 → 红);
|
|
16
|
+
T3 增量不漏账(跨进程 append 可见):外部 append 同内容提案(他进程形态)
|
|
17
|
+
→ 本实例 propose 同内容 dedup 命中新 pid;
|
|
18
|
+
T4 已裁决优先语义保持:外部裁决(accepted)后 propose 同内容 →
|
|
19
|
+
返回已裁决 pid + status;
|
|
20
|
+
T5 旧格式兼容:无 payload_hash 存量行 → 同内容 propose 命中(装载期
|
|
21
|
+
现算一次,不每条重算);
|
|
22
|
+
T6 幂等保形:同内容两次 propose 返回同 pid、不重复入队;
|
|
23
|
+
T7 决策增量可见:外部 append decisions 后 _closed_pids 即含该 pid。
|
|
24
|
+
"""
|
|
25
|
+
import os
|
|
26
|
+
import shutil
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
|
|
30
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
31
|
+
|
|
32
|
+
import md_cg.mdcos as mdcos_mod # noqa: E402
|
|
33
|
+
from md_cg.fsutil import append_jsonl # noqa: E402
|
|
34
|
+
from md_cg.mdcos import MdCGOS, _sig # noqa: E402
|
|
35
|
+
|
|
36
|
+
PASS = 0
|
|
37
|
+
FAIL = 0
|
|
38
|
+
FAILS = []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check(name, cond, detail=""):
|
|
42
|
+
global PASS, FAIL
|
|
43
|
+
if cond:
|
|
44
|
+
PASS += 1
|
|
45
|
+
print(f" OK {name}")
|
|
46
|
+
else:
|
|
47
|
+
FAIL += 1
|
|
48
|
+
FAILS.append(name)
|
|
49
|
+
print(f" FAIL {name} {detail}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def count_full_reads(fn):
|
|
53
|
+
"""打桩 mdcos.read_jsonl 计数(propose/_pid_status 的全量读入口)。"""
|
|
54
|
+
n = 0
|
|
55
|
+
real = mdcos_mod.read_jsonl
|
|
56
|
+
|
|
57
|
+
def _spy(path, *a, **kw):
|
|
58
|
+
nonlocal n
|
|
59
|
+
n += 1
|
|
60
|
+
return real(path, *a, **kw)
|
|
61
|
+
|
|
62
|
+
mdcos_mod.read_jsonl = _spy
|
|
63
|
+
try:
|
|
64
|
+
return fn(), n
|
|
65
|
+
finally:
|
|
66
|
+
mdcos_mod.read_jsonl = real
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main():
|
|
70
|
+
root = tempfile.mkdtemp(prefix="mdcg_prop_guard_")
|
|
71
|
+
try:
|
|
72
|
+
cg = MdCGOS(root)
|
|
73
|
+
# T1 二次 propose 零全量读(预热完成首装——inbox 文件由首条 propose
|
|
74
|
+
# 创建,创建后第一次访问做一次全量装载属预期,装载后稳态零全量读)
|
|
75
|
+
print("== T1 稳态 propose 零全量读 ==")
|
|
76
|
+
cg.propose("t1w1", "T1 预热 甲") # 创建 inbox + 装载(不计数)
|
|
77
|
+
cg.propose("t1w2", "T1 预热 乙") # 装载水位已建立(不计数)
|
|
78
|
+
_, n1 = count_full_reads(lambda: cg.propose("t1", "T1 内容 甲"))
|
|
79
|
+
print(f" (稳态首次计数窗 full_reads={n1})")
|
|
80
|
+
_, n2 = count_full_reads(lambda: cg.propose("t1b", "T1 内容 乙"))
|
|
81
|
+
check("T1a 稳态 propose 全量 read_jsonl 调用 == 0(旧默认 2 次/条)",
|
|
82
|
+
n1 == 0 and n2 == 0, f"full_reads={n1},{n2}")
|
|
83
|
+
|
|
84
|
+
# T2 批量 O(M):500 条连续 propose,装载完成后全量读为 0
|
|
85
|
+
print("== T2 批量 500 条 O(M) ==")
|
|
86
|
+
cg2_root = tempfile.mkdtemp(prefix="mdcg_prop_guard2_")
|
|
87
|
+
cg2 = MdCGOS(cg2_root)
|
|
88
|
+
try:
|
|
89
|
+
cg2.propose("warm1", "预热 占位一") # 创建 inbox + 装载(不计)
|
|
90
|
+
cg2.propose("warm2", "预热 占位二") # 水位建立(不计)
|
|
91
|
+
_, nb = count_full_reads(lambda: [
|
|
92
|
+
cg2.propose("b%03d" % i, "批量守卫 %d 各异 %d" % (i, i * 3))
|
|
93
|
+
for i in range(500)])
|
|
94
|
+
check("T2a 500 条批量 propose 全量 read_jsonl 调用 == 0"
|
|
95
|
+
"(旧代码 2×499=998 次,O(M²) 解析)", nb == 0,
|
|
96
|
+
f"full_reads={nb}")
|
|
97
|
+
finally:
|
|
98
|
+
shutil.rmtree(cg2_root, ignore_errors=True)
|
|
99
|
+
|
|
100
|
+
# T3 增量不漏账:外部(他进程形态)append 同内容提案
|
|
101
|
+
print("== T3 跨进程 append 可见(增量不漏账)==")
|
|
102
|
+
content = "T3 同内容 蜂群调度"
|
|
103
|
+
pid_ext = "prop_ext_abc123"
|
|
104
|
+
append_jsonl(cg.inbox_log,
|
|
105
|
+
{"t": 1.0, "pid": pid_ext, "id": "ext_n",
|
|
106
|
+
"content": content, "layer": "knowledge",
|
|
107
|
+
"tags": [], "condition_space": {}, "verify": {},
|
|
108
|
+
"payload_hash": _sig(content), "actor": "ext"})
|
|
109
|
+
r3 = cg.propose("t3", content, info=True)
|
|
110
|
+
check("T3a 外部 append 后同内容 dedup 命中新 pid",
|
|
111
|
+
r3["dedup"] and r3["pid"] == pid_ext, f"r={r3}")
|
|
112
|
+
|
|
113
|
+
# T4 已裁决优先:外部裁决 accepted → propose 返回已裁决 pid
|
|
114
|
+
print("== T4 已裁决优先 ==")
|
|
115
|
+
append_jsonl(cg.decisions_log,
|
|
116
|
+
{"t": 2.0, "pid": pid_ext, "decision": "accept",
|
|
117
|
+
"status": "accepted", "round": 1, "actor": "ext"})
|
|
118
|
+
r4 = cg.propose("t4", content, info=True)
|
|
119
|
+
check("T4a 外部裁决后同内容返回已裁决 pid(accepted 优先)",
|
|
120
|
+
r4["dedup"] and r4["pid"] == pid_ext
|
|
121
|
+
and r4["dup_status"] == "accepted", f"r={r4}")
|
|
122
|
+
check("T4b 外部裁决进入 _closed_pids(增量可见)",
|
|
123
|
+
pid_ext in cg._closed_pids())
|
|
124
|
+
|
|
125
|
+
# T5 旧格式兼容:无 payload_hash 存量行
|
|
126
|
+
print("== T5 旧格式(无 payload_hash)==")
|
|
127
|
+
legacy = "T5 旧格式内容 星云编译"
|
|
128
|
+
append_jsonl(cg.inbox_log,
|
|
129
|
+
{"t": 3.0, "pid": "prop_legacy_001", "id": "lg_n",
|
|
130
|
+
"content": legacy, "layer": "knowledge",
|
|
131
|
+
"tags": [], "verify": {}, "actor": "old"}) # 无 hash
|
|
132
|
+
r5 = cg.propose("t5", legacy, info=True)
|
|
133
|
+
check("T5a 旧格式行同内容命中(装载期现算,兼容旧账)",
|
|
134
|
+
r5["dedup"] and r5["pid"] == "prop_legacy_001", f"r={r5}")
|
|
135
|
+
_, n5 = count_full_reads(lambda: cg.propose("t5b", legacy, info=True))
|
|
136
|
+
check("T5b 旧格式行不触发每条重算全量读(==0)", n5 == 0,
|
|
137
|
+
f"full_reads={n5}")
|
|
138
|
+
|
|
139
|
+
# T6 幂等保形:同内容两次 propose 同 pid
|
|
140
|
+
print("== T6 幂等保形 ==")
|
|
141
|
+
p_a = cg.propose("t6", "T6 幂等内容")
|
|
142
|
+
p_b = cg.propose("t6r", "T6 幂等内容")
|
|
143
|
+
check("T6a 同内容两次 propose 返回同 pid(不重复入队)",
|
|
144
|
+
p_a == p_b and p_a, f"{p_a} vs {p_b}")
|
|
145
|
+
finally:
|
|
146
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
147
|
+
|
|
148
|
+
print("\n" + "=" * 60)
|
|
149
|
+
print(f"结果:PASS {PASS} / FAIL {FAIL}")
|
|
150
|
+
if FAILS:
|
|
151
|
+
for f in FAILS:
|
|
152
|
+
print(f" - {f}")
|
|
153
|
+
return 0 if FAIL == 0 else 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
sys.exit(main())
|