@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
package/md_cg/test_retr_s1b.py
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
- S1b 开:query 侧推断出候选桶 → 候选收敛(out < in)、**orphan/无桶恒留兜底**、不引入任何新节点
|
|
9
9
|
- 命中不到任何桶 → no_key_match 且不收敛(回退)
|
|
10
10
|
- topk / min_sim 可配(topk=0 关闭;min_sim=1.0 只认完全同名键)
|
|
11
|
+
- issue #33:中文 query × 英文桶键经中文别名收敛(别名=节点自身 tags/正文中文词,
|
|
12
|
+
零翻译依赖);索引重建后口径一致;无别名时审计给 cross_lang_no_match(≠no_key_match)
|
|
11
13
|
- 幂等:重复检索 / 索引重建后同口径
|
|
12
14
|
"""
|
|
13
15
|
import os
|
|
@@ -177,6 +179,71 @@ def main():
|
|
|
177
179
|
check("min_sim=0.34:部分匹配(感知→感知系统 0.5)入选",
|
|
178
180
|
g6.get("keys") == [b_sense], str(g6))
|
|
179
181
|
|
|
182
|
+
# ---- 6b) issue #33:中文 query × 英文桶键(守卫场景按触发条件设计:
|
|
183
|
+
# 既有用例全是同语言对,对跨语言天然免疫——这正是缺陷长期未被发现的根因)
|
|
184
|
+
root_x = tempfile.mkdtemp(prefix="retr_s1bx_")
|
|
185
|
+
cgx = MdCG(root_x)
|
|
186
|
+
cgx.add("sw1", "蜂群调度 依赖门禁 worker 领取", "knowledge",
|
|
187
|
+
tags=["domain:swarm-dispatch"])
|
|
188
|
+
cgx.add("tk1", "令牌校验 签发 载荷", "knowledge",
|
|
189
|
+
tags=["domain:token-verify"])
|
|
190
|
+
cgx.flush()
|
|
191
|
+
e_sw = cgx.index["nodes"]["sw1"]
|
|
192
|
+
check("英文桶键节点落中文别名(自身正文中文词,零翻译)",
|
|
193
|
+
"蜂群调度" in (e_sw.get("bucket_zh") or []),
|
|
194
|
+
str(e_sw.get("bucket_zh")))
|
|
195
|
+
check("中文桶键节点不带别名(无跨语言问题)",
|
|
196
|
+
not (cg.index["nodes"].get("s1") or {}).get("bucket_zh"))
|
|
197
|
+
rx, mx = cgx.search("蜂群调度 依赖", k=10, judge=False, record=False)
|
|
198
|
+
gx = (mx.get("gates") or {}).get("s1b") or {}
|
|
199
|
+
check("issue#33:中文 query × 英文桶键经别名收敛",
|
|
200
|
+
gx.get("keys") == [e_sw["bucket"]], str(gx))
|
|
201
|
+
check("issue#33:收敛后剔除异桶(token 桶不入围)",
|
|
202
|
+
"tk1" not in _ids(rx), str(_ids(rx)))
|
|
203
|
+
cgx.rebuild_index()
|
|
204
|
+
rx2, mx2 = cgx.search("蜂群调度 依赖", k=10, judge=False, record=False)
|
|
205
|
+
gx2 = (mx2.get("gates") or {}).get("s1b") or {}
|
|
206
|
+
check("issue#33:索引重建后别名口径一致(fm 派生)",
|
|
207
|
+
gx2.get("keys") == gx.get("keys"), str(gx2))
|
|
208
|
+
# 止血审计:英文键 + 无中文面(正文也无中文)→ cross_lang_no_match ≠ no_key_match
|
|
209
|
+
root_y = tempfile.mkdtemp(prefix="retr_s1by_")
|
|
210
|
+
cgy = MdCG(root_y)
|
|
211
|
+
cgy.add("p1", "pure english body nothing else", "knowledge",
|
|
212
|
+
tags=["domain:latin-only"])
|
|
213
|
+
cgy.flush()
|
|
214
|
+
gy = (cgy.search("蜂群调度", k=10, judge=False, record=False)[1]
|
|
215
|
+
.get("gates") or {}).get("s1b") or {}
|
|
216
|
+
check("issue#33 止血:跨语言无别名 → cross_lang_no_match",
|
|
217
|
+
gy.get("reason") == "cross_lang_no_match", str(gy))
|
|
218
|
+
gy3 = (cgy.search("zzz qqq", k=10, judge=False, record=False)[1]
|
|
219
|
+
.get("gates") or {}).get("s1b") or {}
|
|
220
|
+
check("issue#33:非跨语言无匹配仍是 no_key_match",
|
|
221
|
+
gy3.get("reason") == "no_key_match", str(gy3))
|
|
222
|
+
# backfill:模拟存量节点(抹掉 fm 与索引两侧别名)→ 回填恢复 → 收敛恢复
|
|
223
|
+
import os as _os
|
|
224
|
+
for _nid in ("sw1", "tk1"): # 两桶别名都抹(cross_lang 判定看全局比较面)
|
|
225
|
+
_p = _os.path.join(cgx.root, cgx.index["nodes"][_nid]["path"])
|
|
226
|
+
_g = cgx.get(_nid)
|
|
227
|
+
_g["frontmatter"].pop("bucket_zh", None)
|
|
228
|
+
cgx._write_node(_nid, _p, _g["frontmatter"], _g["content"])
|
|
229
|
+
cgx.index["nodes"][_nid]["bucket_zh"] = None
|
|
230
|
+
_gy_pre = (cgx.search("蜂群调度 依赖", k=10, judge=False,
|
|
231
|
+
record=False)[1].get("gates") or {}).get("s1b") or {}
|
|
232
|
+
check("抹掉别名后回到 cross_lang_no_match(存量形态复现)",
|
|
233
|
+
_gy_pre.get("reason") == "cross_lang_no_match", str(_gy_pre))
|
|
234
|
+
st_bf = cgx.backfill_bucket_zh()
|
|
235
|
+
check("backfill 恢复别名(幂等统计)",
|
|
236
|
+
st_bf.get("written") == 2
|
|
237
|
+
and "蜂群调度" in (cgx.index["nodes"]["sw1"].get("bucket_zh") or []),
|
|
238
|
+
str(st_bf))
|
|
239
|
+
gxb = (cgx.search("蜂群调度 依赖", k=10, judge=False,
|
|
240
|
+
record=False)[1].get("gates") or {}).get("s1b") or {}
|
|
241
|
+
check("backfill 后中文 query 重新收敛英文桶",
|
|
242
|
+
gxb.get("keys") == [e_sw["bucket"]], str(gxb))
|
|
243
|
+
st_bf2 = cgx.backfill_bucket_zh()
|
|
244
|
+
check("backfill 幂等(二次全 already)",
|
|
245
|
+
st_bf2.get("written") == 0 and st_bf2.get("already") >= 2, str(st_bf2))
|
|
246
|
+
|
|
180
247
|
# ---- 7) 幂等 ----
|
|
181
248
|
_setenv(MDCG_BUCKET_TOPK="1", MDCG_BUCKET_MIN_SIM="0.34")
|
|
182
249
|
ra, ma = cg.search(QUERY, k=10, judge=False, record=False)
|
package/md_cg/test_retr_s7.py
CHANGED
|
@@ -253,6 +253,14 @@ def main():
|
|
|
253
253
|
# 改写已有节点(节点数不变 → 只可能是目录 mtime 变化)
|
|
254
254
|
cg5.add("n1", "阿尔法 贝塔 伽马 追加", "knowledge")
|
|
255
255
|
cg5.flush()
|
|
256
|
+
# mtime 粒度守卫(批次 26):指纹用 st_mtime_ns,但同刻写入在负载波动下
|
|
257
|
+
# 可能落进同粒度(实测 43/0 与 41/2 交替——负载敏感 flaky,批次 20 同类)。
|
|
258
|
+
# 断言目标是「改写能被发现」机制而非文件系统粒度——显式前移 mtime 保证确定性。
|
|
259
|
+
_t = time.time_ns() + 60_000_000_000 # +60s
|
|
260
|
+
for _d, _ds, _fs in os.walk(os.path.join(root5, "knowledge")):
|
|
261
|
+
os.utime(_d, ns=(_t, _t))
|
|
262
|
+
for _f in _fs:
|
|
263
|
+
os.utime(os.path.join(_d, _f), ns=(_t, _t))
|
|
256
264
|
_sr = postings.stale_reason(root5, cg5.index["nodes"])
|
|
257
265
|
check("改写后:指纹判定过期(dir_touched)", _sr == "dir_touched", _sr)
|
|
258
266
|
_setenv(MDCG_GATE_S7_POSTINGS=None)
|
|
@@ -1,175 +1,181 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""S9b 三元组反查 · 工具面集成探针(阶段二 4.2 T9,脚本式,exit code 定成败)。
|
|
3
|
-
|
|
4
|
-
用法:python -X utf8 -m md_cg.test_retr_s9_entity_ctx
|
|
5
|
-
库层原语语义归 `test_retr_s9_edges.py`,本件只测**工具面集成**:
|
|
6
|
-
- 分派全链路:`_cg_dispatch` / `_cg_call` → `_edges_call` → `provenance.find_edges`
|
|
7
|
-
- op 缺省推导防呆负例:`child=` 不构成 edges 签名,漏 op 走 read 不冒充反查
|
|
8
|
-
- 权限闸双面:`ops_allow` 含 edges 放行;不含 → AccessDenied(通配 "*" 不限)
|
|
9
|
-
- 渐进披露:slim 后 op=edges 段被投影、职责行 ≤160;`op=help,query=edges` 无损取回
|
|
10
|
-
- schema 一致性:扁平参数面与库层签名同形(含 expand_nodes);无 subject/predicate/object 第二套参数
|
|
11
|
-
- 协议对账:`protocol.audit` AST 自动发现 edges(extension_ops 报告项,ok=True)
|
|
12
|
-
- FUNC_DESC 覆盖守卫:cogmap_sync 功能描述表必须登记 ("cg", "edges")
|
|
13
|
-
"""
|
|
14
|
-
import inspect
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import sys
|
|
18
|
-
import tempfile
|
|
19
|
-
|
|
20
|
-
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
21
|
-
|
|
22
|
-
from md_cg import provenance, protocol, tool_face as tf # noqa: E402
|
|
23
|
-
from md_cg.mdcg import MdCG # noqa: E402
|
|
24
|
-
from md_cg.mdcos import MdCGSecure # noqa: E402
|
|
25
|
-
from md_cg.security import AccessDenied, Principal # noqa: E402
|
|
26
|
-
from md_cg import mcp_server as ms # noqa: E402
|
|
27
|
-
|
|
28
|
-
passed = 0
|
|
29
|
-
failed = 0
|
|
30
|
-
T = "阿尔法 三元组 反查 探针"
|
|
31
|
-
|
|
32
|
-
# _edges_call 透传的扁平参数面(schema ↔ 实现同形守卫的期望集)
|
|
33
|
-
EDGE_ARGS = ("child", "parent", "relation", "batch",
|
|
34
|
-
"start_time", "end_time", "start_operator", "end_operator",
|
|
35
|
-
"time_axis", "ordering", "offset", "limit", "aggregation",
|
|
36
|
-
"expand_nodes")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def check(name, cond, detail=""):
|
|
40
|
-
global passed, failed
|
|
41
|
-
if cond:
|
|
42
|
-
passed += 1
|
|
43
|
-
print(" [PASS] " + name)
|
|
44
|
-
else:
|
|
45
|
-
failed += 1
|
|
46
|
-
print(" [FAIL] " + name + " " + detail)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _build(root):
|
|
50
|
-
cg = MdCG(root)
|
|
51
|
-
for nid in ("p1", "p2"):
|
|
52
|
-
cg.add(nid, T, "knowledge")
|
|
53
|
-
cg.add("a1", T, "knowledge", derived_from=["p1"])
|
|
54
|
-
cg.add("a2", T, "knowledge", derived_from=["p1"], relation="extracted_from")
|
|
55
|
-
cg.flush()
|
|
56
|
-
return cg
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _raises(exc, fn):
|
|
60
|
-
try:
|
|
61
|
-
fn()
|
|
62
|
-
except exc:
|
|
63
|
-
return True
|
|
64
|
-
except Exception: # noqa: BLE001
|
|
65
|
-
return False
|
|
66
|
-
return False
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def main():
|
|
70
|
-
root = tempfile.mkdtemp(prefix="retr_s9b_")
|
|
71
|
-
cg = _build(root)
|
|
72
|
-
|
|
73
|
-
# ================= 1) 分派全链路 =================
|
|
74
|
-
r = ms._cg_dispatch(cg, {"op": "edges", "child": "a1"})
|
|
75
|
-
check("dispatch 直调:op=edges → _edges_call → find_edges(ok/readonly/total)",
|
|
76
|
-
isinstance(r, dict) and r.get("ok") is True and r.get("readonly") is True
|
|
77
|
-
and r.get("op") == "edges" and r.get("total") == 1
|
|
78
|
-
and r["edges"][0]["child"] == "a1", str(r)[:200])
|
|
79
|
-
|
|
80
|
-
rc = ms._cg_call(cg, {"op": "edges", "child": "a2", "expand_nodes": True})
|
|
81
|
-
check("_cg_call 外层透传 expand_nodes:边附端点摘要(a2 在库 / p1 在库)",
|
|
82
|
-
rc.get("ok") is True and rc.get("total") == 1
|
|
83
|
-
and rc["edges"][0]["child_node"]["present"] is True
|
|
84
|
-
and rc["edges"][0]["parent_node"]["present"] is True,
|
|
85
|
-
str(rc.get("edges", [{}])[0].get("child_node")))
|
|
86
|
-
|
|
87
|
-
# ================= 2) op 缺省推导防呆(负例)=================
|
|
88
|
-
rd = ms._cg_call(cg, {"child": "a1"})
|
|
89
|
-
check("漏 op 不冒充反查:child= 不是 edges 签名 → 按既有规则推导为 read(op_derived 透出)",
|
|
90
|
-
rd.get("op") == "read" and rd.get("op_derived") is True,
|
|
91
|
-
str({k: rd.get(k) for k in ("op", "op_derived")}))
|
|
92
|
-
check("显式 op 优先:op=edges 时 op_derived 不出现(推导只兜底不覆盖)",
|
|
93
|
-
"op_derived" not in rc)
|
|
94
|
-
|
|
95
|
-
# ================= 3) 权限闸双面 =================
|
|
96
|
-
p_ok = Principal(actor="anon", role="anonymous", can_write=False,
|
|
97
|
-
ops_allow=("info", "route", "read", "edges"))
|
|
98
|
-
p_no = Principal(actor="narrow", role="reader", can_write=False,
|
|
99
|
-
ops_allow=("info", "route", "read"))
|
|
100
|
-
cg_ok = MdCGSecure(root, principal=p_ok)
|
|
101
|
-
cg_no = MdCGSecure(root, principal=p_no)
|
|
102
|
-
check("ops_allow 含 edges → 放行(只读结果正常返回)",
|
|
103
|
-
ms._cg_dispatch(cg_ok, {"op": "edges", "parent": "p1"}).get("total") == 2)
|
|
104
|
-
check("ops_allow 不含 edges → AccessDenied(角色作用域闸,不静默降级)",
|
|
105
|
-
_raises(AccessDenied, lambda: ms._cg_dispatch(cg_no, {"op": "edges"})),
|
|
106
|
-
"expected AccessDenied")
|
|
107
|
-
check("通配白名单不受新 op 影响:ops_allow=('*',) 放行(designer 形态)",
|
|
108
|
-
ms._cg_dispatch(
|
|
109
|
-
MdCGSecure(root, principal=Principal(
|
|
110
|
-
actor="boss", role="designer", can_write=True, can_admin=True,
|
|
111
|
-
ops_allow=("*",))),
|
|
112
|
-
{"op": "edges", "relation": "derived_from"})["total"] == 1)
|
|
113
|
-
|
|
114
|
-
# ================= 4) 渐进披露(投影 ↔ help 回取)=================
|
|
115
|
-
slim = {t["name"]: t for t in tf.slim_tools(ms.ALL_TOOLS)}
|
|
116
|
-
cg_slim_desc = slim["cg"].get("description") or ""
|
|
117
|
-
check("slim 后 op=edges 段被投影(常驻上下文不付费)",
|
|
118
|
-
"op=edges:" not in cg_slim_desc and "edges" not in tf.op_sections(cg_slim_desc),
|
|
119
|
-
cg_slim_desc[:120])
|
|
120
|
-
check("slim 后 cg 职责行仍 ≤160 字符(投影不破尺寸预算)",
|
|
121
|
-
len(cg_slim_desc) <= 160, str(len(cg_slim_desc)))
|
|
122
|
-
h = tf.help_text(ms.ALL_TOOLS, query="edges")
|
|
123
|
-
hit = next((x for x in h.get("hits", []) if x.get("op") == "edges"), None)
|
|
124
|
-
check("op=help,query=edges 无损取回:命中 op 段落与透传参数清单(expand_nodes 在列)",
|
|
125
|
-
h.get("ok") is True and hit is not None
|
|
126
|
-
and "expand_nodes" in json.dumps(hit.get("params") or []),
|
|
127
|
-
str(h)[:200])
|
|
128
|
-
|
|
129
|
-
# ================= 5) schema 一致性 =================
|
|
130
|
-
cg_tool = next(t for t in ms.ALL_TOOLS if t["name"] == "cg")
|
|
131
|
-
isch = cg_tool.get("inputSchema") or {}
|
|
132
|
-
props = isch.get("properties") or {}
|
|
133
|
-
req = set(isch.get("required") or [])
|
|
134
|
-
check("schema:edges 全部透传参数在列且均为可选(required 不含)",
|
|
135
|
-
all(k in props for k in EDGE_ARGS)
|
|
136
|
-
and not (req & set(EDGE_ARGS)),
|
|
137
|
-
str([k for k in EDGE_ARGS if k not in props]))
|
|
138
|
-
check("schema:predicate/object 不进参数面(subject 是 identity/self_state/link 既有主体参数,非 edges 引入)",
|
|
139
|
-
not ({"predicate", "object"} & set(props)))
|
|
140
|
-
_ec_src = inspect.getsource(ms._edges_call)
|
|
141
|
-
check("实现面:_edges_call 不读 subject/predicate/object 第二套参数键(术语映射只在 docstring)",
|
|
142
|
-
not any(('"%s"' % k) in _ec_src or ("'%s'" % k) in _ec_src
|
|
143
|
-
for k in ("subject", "predicate", "object")),
|
|
144
|
-
str([k for k in ("subject", "predicate", "object")
|
|
145
|
-
if k in _ec_src]))
|
|
146
|
-
check("实现签名:find_edges 用 expand_nodes(旧名 expand 已废除,防回潮)",
|
|
147
|
-
"expand_nodes" in inspect.signature(provenance.find_edges).parameters
|
|
148
|
-
and "expand" not in inspect.signature(provenance.find_edges).parameters,
|
|
149
|
-
str(list(inspect.signature(provenance.find_edges).parameters)))
|
|
150
|
-
|
|
151
|
-
# ================= 6) 协议对账(AST 自动发现)=================
|
|
152
|
-
au = protocol.audit()
|
|
153
|
-
check("protocol.audit:ok=True、shape_errors 空(edges 属扩展面不违例)",
|
|
154
|
-
au.get("ok") is True and not au.get("shape_errors")
|
|
155
|
-
and not au.get("errors"), str(au.get("errors")))
|
|
156
|
-
check("protocol.audit:AST 自动发现 edges 分支(extension_ops 报告项在列)",
|
|
157
|
-
"edges" in (au.get("actual") or [])
|
|
158
|
-
and "edges" in (au.get("extension_ops") or []),
|
|
159
|
-
str(au.get("actual")))
|
|
160
|
-
|
|
161
|
-
# ================= 7) FUNC_DESC 覆盖守卫 =================
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""S9b 三元组反查 · 工具面集成探针(阶段二 4.2 T9,脚本式,exit code 定成败)。
|
|
3
|
+
|
|
4
|
+
用法:python -X utf8 -m md_cg.test_retr_s9_entity_ctx
|
|
5
|
+
库层原语语义归 `test_retr_s9_edges.py`,本件只测**工具面集成**:
|
|
6
|
+
- 分派全链路:`_cg_dispatch` / `_cg_call` → `_edges_call` → `provenance.find_edges`
|
|
7
|
+
- op 缺省推导防呆负例:`child=` 不构成 edges 签名,漏 op 走 read 不冒充反查
|
|
8
|
+
- 权限闸双面:`ops_allow` 含 edges 放行;不含 → AccessDenied(通配 "*" 不限)
|
|
9
|
+
- 渐进披露:slim 后 op=edges 段被投影、职责行 ≤160;`op=help,query=edges` 无损取回
|
|
10
|
+
- schema 一致性:扁平参数面与库层签名同形(含 expand_nodes);无 subject/predicate/object 第二套参数
|
|
11
|
+
- 协议对账:`protocol.audit` AST 自动发现 edges(extension_ops 报告项,ok=True)
|
|
12
|
+
- FUNC_DESC 覆盖守卫:cogmap_sync 功能描述表必须登记 ("cg", "edges")
|
|
13
|
+
"""
|
|
14
|
+
import inspect
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
21
|
+
|
|
22
|
+
from md_cg import provenance, protocol, tool_face as tf # noqa: E402
|
|
23
|
+
from md_cg.mdcg import MdCG # noqa: E402
|
|
24
|
+
from md_cg.mdcos import MdCGSecure # noqa: E402
|
|
25
|
+
from md_cg.security import AccessDenied, Principal # noqa: E402
|
|
26
|
+
from md_cg import mcp_server as ms # noqa: E402
|
|
27
|
+
|
|
28
|
+
passed = 0
|
|
29
|
+
failed = 0
|
|
30
|
+
T = "阿尔法 三元组 反查 探针"
|
|
31
|
+
|
|
32
|
+
# _edges_call 透传的扁平参数面(schema ↔ 实现同形守卫的期望集)
|
|
33
|
+
EDGE_ARGS = ("child", "parent", "relation", "batch",
|
|
34
|
+
"start_time", "end_time", "start_operator", "end_operator",
|
|
35
|
+
"time_axis", "ordering", "offset", "limit", "aggregation",
|
|
36
|
+
"expand_nodes")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def check(name, cond, detail=""):
|
|
40
|
+
global passed, failed
|
|
41
|
+
if cond:
|
|
42
|
+
passed += 1
|
|
43
|
+
print(" [PASS] " + name)
|
|
44
|
+
else:
|
|
45
|
+
failed += 1
|
|
46
|
+
print(" [FAIL] " + name + " " + detail)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _build(root):
|
|
50
|
+
cg = MdCG(root)
|
|
51
|
+
for nid in ("p1", "p2"):
|
|
52
|
+
cg.add(nid, T, "knowledge")
|
|
53
|
+
cg.add("a1", T, "knowledge", derived_from=["p1"])
|
|
54
|
+
cg.add("a2", T, "knowledge", derived_from=["p1"], relation="extracted_from")
|
|
55
|
+
cg.flush()
|
|
56
|
+
return cg
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _raises(exc, fn):
|
|
60
|
+
try:
|
|
61
|
+
fn()
|
|
62
|
+
except exc:
|
|
63
|
+
return True
|
|
64
|
+
except Exception: # noqa: BLE001
|
|
65
|
+
return False
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main():
|
|
70
|
+
root = tempfile.mkdtemp(prefix="retr_s9b_")
|
|
71
|
+
cg = _build(root)
|
|
72
|
+
|
|
73
|
+
# ================= 1) 分派全链路 =================
|
|
74
|
+
r = ms._cg_dispatch(cg, {"op": "edges", "child": "a1"})
|
|
75
|
+
check("dispatch 直调:op=edges → _edges_call → find_edges(ok/readonly/total)",
|
|
76
|
+
isinstance(r, dict) and r.get("ok") is True and r.get("readonly") is True
|
|
77
|
+
and r.get("op") == "edges" and r.get("total") == 1
|
|
78
|
+
and r["edges"][0]["child"] == "a1", str(r)[:200])
|
|
79
|
+
|
|
80
|
+
rc = ms._cg_call(cg, {"op": "edges", "child": "a2", "expand_nodes": True})
|
|
81
|
+
check("_cg_call 外层透传 expand_nodes:边附端点摘要(a2 在库 / p1 在库)",
|
|
82
|
+
rc.get("ok") is True and rc.get("total") == 1
|
|
83
|
+
and rc["edges"][0]["child_node"]["present"] is True
|
|
84
|
+
and rc["edges"][0]["parent_node"]["present"] is True,
|
|
85
|
+
str(rc.get("edges", [{}])[0].get("child_node")))
|
|
86
|
+
|
|
87
|
+
# ================= 2) op 缺省推导防呆(负例)=================
|
|
88
|
+
rd = ms._cg_call(cg, {"child": "a1"})
|
|
89
|
+
check("漏 op 不冒充反查:child= 不是 edges 签名 → 按既有规则推导为 read(op_derived 透出)",
|
|
90
|
+
rd.get("op") == "read" and rd.get("op_derived") is True,
|
|
91
|
+
str({k: rd.get(k) for k in ("op", "op_derived")}))
|
|
92
|
+
check("显式 op 优先:op=edges 时 op_derived 不出现(推导只兜底不覆盖)",
|
|
93
|
+
"op_derived" not in rc)
|
|
94
|
+
|
|
95
|
+
# ================= 3) 权限闸双面 =================
|
|
96
|
+
p_ok = Principal(actor="anon", role="anonymous", can_write=False,
|
|
97
|
+
ops_allow=("info", "route", "read", "edges"))
|
|
98
|
+
p_no = Principal(actor="narrow", role="reader", can_write=False,
|
|
99
|
+
ops_allow=("info", "route", "read"))
|
|
100
|
+
cg_ok = MdCGSecure(root, principal=p_ok)
|
|
101
|
+
cg_no = MdCGSecure(root, principal=p_no)
|
|
102
|
+
check("ops_allow 含 edges → 放行(只读结果正常返回)",
|
|
103
|
+
ms._cg_dispatch(cg_ok, {"op": "edges", "parent": "p1"}).get("total") == 2)
|
|
104
|
+
check("ops_allow 不含 edges → AccessDenied(角色作用域闸,不静默降级)",
|
|
105
|
+
_raises(AccessDenied, lambda: ms._cg_dispatch(cg_no, {"op": "edges"})),
|
|
106
|
+
"expected AccessDenied")
|
|
107
|
+
check("通配白名单不受新 op 影响:ops_allow=('*',) 放行(designer 形态)",
|
|
108
|
+
ms._cg_dispatch(
|
|
109
|
+
MdCGSecure(root, principal=Principal(
|
|
110
|
+
actor="boss", role="designer", can_write=True, can_admin=True,
|
|
111
|
+
ops_allow=("*",))),
|
|
112
|
+
{"op": "edges", "relation": "derived_from"})["total"] == 1)
|
|
113
|
+
|
|
114
|
+
# ================= 4) 渐进披露(投影 ↔ help 回取)=================
|
|
115
|
+
slim = {t["name"]: t for t in tf.slim_tools(ms.ALL_TOOLS)}
|
|
116
|
+
cg_slim_desc = slim["cg"].get("description") or ""
|
|
117
|
+
check("slim 后 op=edges 段被投影(常驻上下文不付费)",
|
|
118
|
+
"op=edges:" not in cg_slim_desc and "edges" not in tf.op_sections(cg_slim_desc),
|
|
119
|
+
cg_slim_desc[:120])
|
|
120
|
+
check("slim 后 cg 职责行仍 ≤160 字符(投影不破尺寸预算)",
|
|
121
|
+
len(cg_slim_desc) <= 160, str(len(cg_slim_desc)))
|
|
122
|
+
h = tf.help_text(ms.ALL_TOOLS, query="edges")
|
|
123
|
+
hit = next((x for x in h.get("hits", []) if x.get("op") == "edges"), None)
|
|
124
|
+
check("op=help,query=edges 无损取回:命中 op 段落与透传参数清单(expand_nodes 在列)",
|
|
125
|
+
h.get("ok") is True and hit is not None
|
|
126
|
+
and "expand_nodes" in json.dumps(hit.get("params") or []),
|
|
127
|
+
str(h)[:200])
|
|
128
|
+
|
|
129
|
+
# ================= 5) schema 一致性 =================
|
|
130
|
+
cg_tool = next(t for t in ms.ALL_TOOLS if t["name"] == "cg")
|
|
131
|
+
isch = cg_tool.get("inputSchema") or {}
|
|
132
|
+
props = isch.get("properties") or {}
|
|
133
|
+
req = set(isch.get("required") or [])
|
|
134
|
+
check("schema:edges 全部透传参数在列且均为可选(required 不含)",
|
|
135
|
+
all(k in props for k in EDGE_ARGS)
|
|
136
|
+
and not (req & set(EDGE_ARGS)),
|
|
137
|
+
str([k for k in EDGE_ARGS if k not in props]))
|
|
138
|
+
check("schema:predicate/object 不进参数面(subject 是 identity/self_state/link 既有主体参数,非 edges 引入)",
|
|
139
|
+
not ({"predicate", "object"} & set(props)))
|
|
140
|
+
_ec_src = inspect.getsource(ms._edges_call)
|
|
141
|
+
check("实现面:_edges_call 不读 subject/predicate/object 第二套参数键(术语映射只在 docstring)",
|
|
142
|
+
not any(('"%s"' % k) in _ec_src or ("'%s'" % k) in _ec_src
|
|
143
|
+
for k in ("subject", "predicate", "object")),
|
|
144
|
+
str([k for k in ("subject", "predicate", "object")
|
|
145
|
+
if k in _ec_src]))
|
|
146
|
+
check("实现签名:find_edges 用 expand_nodes(旧名 expand 已废除,防回潮)",
|
|
147
|
+
"expand_nodes" in inspect.signature(provenance.find_edges).parameters
|
|
148
|
+
and "expand" not in inspect.signature(provenance.find_edges).parameters,
|
|
149
|
+
str(list(inspect.signature(provenance.find_edges).parameters)))
|
|
150
|
+
|
|
151
|
+
# ================= 6) 协议对账(AST 自动发现)=================
|
|
152
|
+
au = protocol.audit()
|
|
153
|
+
check("protocol.audit:ok=True、shape_errors 空(edges 属扩展面不违例)",
|
|
154
|
+
au.get("ok") is True and not au.get("shape_errors")
|
|
155
|
+
and not au.get("errors"), str(au.get("errors")))
|
|
156
|
+
check("protocol.audit:AST 自动发现 edges 分支(extension_ops 报告项在列)",
|
|
157
|
+
"edges" in (au.get("actual") or [])
|
|
158
|
+
and "edges" in (au.get("extension_ops") or []),
|
|
159
|
+
str(au.get("actual")))
|
|
160
|
+
|
|
161
|
+
# ================= 7) FUNC_DESC 覆盖守卫 =================
|
|
162
|
+
# cogmap_sync 是**源码树**门禁脚本(出货包 files 不含 scripts/)→ 安装态
|
|
163
|
+
# 如实 SKIP:缺件不是被测缺陷,也不虚报通过(2026-09-24 修复)。
|
|
164
|
+
sync_path = os.path.join(os.path.dirname(os.path.dirname(
|
|
165
|
+
os.path.abspath(__file__))), "scripts", "cogmap_sync.py")
|
|
166
|
+
if not os.path.isfile(sync_path):
|
|
167
|
+
print(" SKIP cogmap_sync.FUNC_DESC 覆盖守卫(scripts/cogmap_sync.py "
|
|
168
|
+
"不在——源码树门禁脚本,出货包不含)")
|
|
169
|
+
else:
|
|
170
|
+
sync_src = open(sync_path, encoding="utf-8").read()
|
|
171
|
+
check("cogmap_sync.FUNC_DESC 登记 (\"cg\", \"edges\")(功能调用映射表覆盖门禁的前置)",
|
|
172
|
+
'("cg", "edges")' in sync_src)
|
|
173
|
+
|
|
174
|
+
print("\ntest_retr_s9_entity_ctx: %d 通过 / %d 失败" % (passed, failed))
|
|
175
|
+
return 0 if failed == 0 else 1
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
180
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
181
|
+
sys.exit(main())
|