@furongjun1999/dsh-memory 0.6.0 → 0.6.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 +588 -572
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/discipline/harnesses.yaml +31 -0
- package/docs/discipline/templates/full.md.tmpl +1 -1
- package/docs/discipline/templates/rules.mdc.tmpl +1 -1
- package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +418 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +441 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +398 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +498 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +669 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +683 -0
- package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +205 -0
- package/docs/eval//346/200/247/350/203/275/344/270/223/351/241/271_/345/206/267/346/237/245/350/257/242/344/270/216/345/206/205/345/255/230_v1.0.md +218 -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 +10 -0
- package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +646 -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_v20.md +283 -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_v21.md +224 -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_v22.md +223 -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_v23.md +293 -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_v24.md +303 -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_v25.md +230 -0
- package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +20 -0
- package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +12 -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/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +21 -9
- package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +7 -2
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +39 -3
- package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +220 -0
- package/docs/plans//347/234/237/346/272/220/347/264/242/345/274/225_/351/200/232/347/224/250/346/234/272/345/210/266_v0.3.md +227 -0
- package/docs/plans//350/234/202/345/267/242/346/250/241/345/236/213/345/257/206/351/222/245/351/205/215/347/275/256/351/235/242_v1.0.md +385 -0
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +460 -460
- package/lib/bridge.js +24 -2
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.js +16 -1
- package/lib/lib/prompt_safety.d.ts +52 -1
- package/lib/lib/prompt_safety.js +76 -1
- package/md_cg/audit.py +652 -379
- package/md_cg/bench6_arms.py +1 -1
- package/md_cg/bench_p0.py +1 -1
- package/md_cg/branches.py +2 -2
- package/md_cg/ccgc.py +1071 -1006
- package/md_cg/chain.py +1 -1
- package/md_cg/consistency.py +97 -9
- package/md_cg/consolidate.py +34 -9
- package/md_cg/crypto.py +59 -26
- package/md_cg/docindex.py +16 -1
- package/md_cg/evolution.py +17 -1
- package/md_cg/export.py +1 -1
- package/md_cg/forgetting.py +309 -13
- package/md_cg/fsutil.py +454 -8
- package/md_cg/identity.py +3 -3
- package/md_cg/insight.py +24 -3
- package/md_cg/linkref.py +1 -1
- package/md_cg/logref.py +327 -0
- package/md_cg/mcp_server.py +4152 -3818
- package/md_cg/mdcg.py +4208 -3547
- package/md_cg/mdcos.py +4491 -4159
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +639 -575
- package/md_cg/protect.py +158 -11
- package/md_cg/protocol.py +41 -2
- package/md_cg/provenance.py +22 -4
- package/md_cg/reach.py +4 -4
- package/md_cg/readcache.py +76 -15
- package/md_cg/reconcile.py +1 -1
- package/md_cg/refindex.py +246 -37
- package/md_cg/refine.py +1 -1
- package/md_cg/routing.py +32 -5
- package/md_cg/run_tests.py +17 -0
- package/md_cg/scrub.py +30 -3
- package/md_cg/security.py +47 -1
- package/md_cg/self_state.py +5 -5
- package/md_cg/sources.py +23 -6
- package/md_cg/srcindex.py +352 -0
- package/md_cg/stg.py +47 -3
- package/md_cg/subgraph.py +2 -2
- package/md_cg/sustain.py +1299 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_b1_auto_id_multiproc.py +277 -0
- package/md_cg/test_b1b2_write_face.py +417 -0
- package/md_cg/test_b2_sensitivity_landing.py +223 -0
- package/md_cg/test_b3_merge_keeps_content.py +576 -0
- package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
- package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
- package/md_cg/test_c3_transient_read_negative.py +793 -0
- package/md_cg/test_c8_search_rrf_gates.py +502 -0
- package/md_cg/test_ccg_form_parity.py +188 -0
- package/md_cg/test_ccgc.py +28 -3
- package/md_cg/test_govern_directread.py +17 -4
- package/md_cg/test_h2_session_view_norm.py +233 -0
- package/md_cg/test_h4_sustain_snapshot.py +1377 -0
- package/md_cg/test_hive_ingest.py +285 -285
- package/md_cg/test_issue39_utf8_stdio.py +307 -16
- package/md_cg/test_issue43_default_policy.py +865 -0
- package/md_cg/test_legacy_p3_node_id_type.py +375 -0
- package/md_cg/test_linkref.py +10 -3
- package/md_cg/test_lock.py +2 -2
- package/md_cg/test_logref.py +1109 -0
- package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
- package/md_cg/test_m3_h9_semantic_guard.py +525 -0
- package/md_cg/test_mr_m2.py +15 -3
- package/md_cg/test_n130_verify_falsified_protect.py +1 -1
- package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
- package/md_cg/test_n176_link_trust.py +221 -0
- package/md_cg/test_n178_units_jobid_gate.py +212 -0
- package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
- package/md_cg/test_n195_writepath_reload.py +283 -0
- package/md_cg/test_n196_stale_gate_skip.py +169 -0
- package/md_cg/test_n197_n208_write_face_gates.py +471 -0
- package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
- package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
- package/md_cg/test_n201_proposal_visibility.py +382 -0
- package/md_cg/test_n202_session_notes_visibility.py +461 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
- package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
- package/md_cg/test_n209_verify_write_face_gates.py +461 -0
- package/md_cg/test_n212_n213_n224_generation_gates.py +513 -0
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
- package/md_cg/test_n225_nonobject_load.py +1110 -0
- package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
- package/md_cg/test_neg_condition_hits.py +333 -0
- package/md_cg/test_neg_tail_honesty.py +663 -0
- package/md_cg/test_none_id_write_guard.py +4 -3
- package/md_cg/test_opt_batch1_md_cg.py +451 -0
- package/md_cg/test_p1.py +5 -5
- package/md_cg/test_p11_consistency.py +161 -12
- package/md_cg/test_p26_refindex.py +2 -2
- package/md_cg/test_p27_docindex.py +777 -774
- package/md_cg/test_p28_refcheck.py +740 -13
- package/md_cg/test_p29_session_ingest_export.py +2 -2
- package/md_cg/test_p2_mcp.py +12 -1
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- package/md_cg/test_p9c_dedup_hints.py +276 -0
- package/md_cg/test_policy_required_ccg.py +426 -0
- package/md_cg/test_protocol.py +22 -0
- package/md_cg/test_rank_parity_score_mode.py +516 -0
- package/md_cg/test_read_face_input_gates.py +363 -0
- package/md_cg/test_read_face_semantics.py +489 -0
- package/md_cg/test_recall_face_guards.py +812 -0
- package/md_cg/test_rejected_credential_forms.py +188 -0
- package/md_cg/test_rejected_redact.py +146 -0
- package/md_cg/test_retr_s1b.py +2 -2
- package/md_cg/test_retr_s5.py +20 -7
- package/md_cg/test_review_conformance.py +21 -3
- package/md_cg/test_security_audit_v21.py +2 -2
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_v21r1_package_version.py +310 -0
- package/md_cg/test_writepipe.py +10 -4
- package/md_cg/tokens.py +225 -95
- package/md_cg/tool_face.py +4 -4
- package/md_cg/trust.py +49 -5
- package/md_cg/units.py +204 -6
- package/md_cg/weights.py +4 -4
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
- package/md_cg/writelimit.py +42 -8
- package/md_cg/writepipe.py +651 -554
- package/package.json +3 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/hooks.ts +229 -16
- package/src/init.ts +15 -1
- package/src/lib/prompt_safety.ts +88 -1
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""CCG 六要素「形态口径一致性」验收(2026-09-28)。
|
|
3
|
+
|
|
4
|
+
运行:python -m md_cg.test_ccg_form_parity
|
|
5
|
+
|
|
6
|
+
为什么需要这一套(本套的由来,是一个真实的口径分叉):
|
|
7
|
+
六要素标题存在两种书写形态——带冒号 `# 生效条件:<值>` 与不带冒号
|
|
8
|
+
`# 生效条件` 后换行接值。写入闸门 `data/policy.json` 的必需正则
|
|
9
|
+
`(?m)^#\\s*生效条件` **不要求冒号**,而检索面判据 `nodefile.ccg_completeness`
|
|
10
|
+
原先**要求冒号**才计入「已声明」。二者对同一条正文给出相反结论:归档过闸
|
|
11
|
+
(ACCEPT)后到检索路由被判 BLINDSPOT「CCG 要素不全」——归档看着写成了,
|
|
12
|
+
检索面却当它没声明。存量实测 6905 件含六要素节点中 88 件处于该状态。
|
|
13
|
+
|
|
14
|
+
判据收敛在**单点** `nodefile.ccg_mark_present` / `nodefile.ccg_field_value`
|
|
15
|
+
(冒号可有可无),`mdcos._ccg_field`、`ccgc._has_ccg_line`、
|
|
16
|
+
`consolidate._has_ccg_line`、`tasks._field_line` 一律委托该单点。
|
|
17
|
+
|
|
18
|
+
本套的六组断言:
|
|
19
|
+
① 两形态都判齐(形态不改变「齐不齐」)
|
|
20
|
+
② 缺要素仍判不全(放宽的是标点,不是要求——判别力自证,两种形态各测)
|
|
21
|
+
③ 写入闸门与检索面**结论一致**(读真源 data/policy.json 复算,直接钉住分叉)
|
|
22
|
+
④ 取值面同源:无冒号的「值」= 标题后首个非空非标题行
|
|
23
|
+
⑤ 四个副本函数与单点结论一致(防未来再长出第五、第六套口径)
|
|
24
|
+
⑥ 写读闭环:无冒号节点经 upsert 后取得到新值(不出现「写进去读不出」)
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import io
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
from . import ccgc, consolidate, nodefile, tasks
|
|
35
|
+
from .mdcos import _ccg_field
|
|
36
|
+
|
|
37
|
+
PASS = FAIL = 0
|
|
38
|
+
FAILS = []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ok(cond, label):
|
|
42
|
+
global PASS, FAIL
|
|
43
|
+
if cond:
|
|
44
|
+
PASS += 1
|
|
45
|
+
print(" [PASS] %s" % label)
|
|
46
|
+
else:
|
|
47
|
+
FAIL += 1
|
|
48
|
+
FAILS.append(label)
|
|
49
|
+
print(" [FAIL] %s" % label)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
MARKS = ("功能名", "生效条件", "子功能", "执行", "验证方式", "不适用条件")
|
|
53
|
+
|
|
54
|
+
# ① 带冒号形态(既有惯例形态)
|
|
55
|
+
COLON = "".join("# %s:值_%s\n" % (m, m) for m in MARKS)
|
|
56
|
+
# ① 不带冒号形态(policy 正则接受,标题行 + 换行接值)
|
|
57
|
+
NONCOLON = "".join("# %s\n值_%s\n" % (m, m) for m in MARKS)
|
|
58
|
+
# ① 混形态(一半带冒号一半不带)
|
|
59
|
+
MIXED = "".join(("# %s:值_%s\n" if i % 2 == 0 else "# %s\n值_%s\n") % (m, m)
|
|
60
|
+
for i, m in enumerate(MARKS))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _drop(body: str, mark: str) -> str:
|
|
64
|
+
"""删掉某要素的标题行(连带其值行)——用于②的判别力自证。"""
|
|
65
|
+
out = []
|
|
66
|
+
skip_next = False
|
|
67
|
+
for ln in body.splitlines(True):
|
|
68
|
+
if skip_next:
|
|
69
|
+
skip_next = False
|
|
70
|
+
continue
|
|
71
|
+
if re.match(r"^#\s*%s\s*[::]?\s*$" % re.escape(mark), ln.rstrip("\n")):
|
|
72
|
+
skip_next = True
|
|
73
|
+
continue
|
|
74
|
+
if ln.startswith("# %s:" % mark) or ln.startswith("# %s:" % mark):
|
|
75
|
+
continue
|
|
76
|
+
out.append(ln)
|
|
77
|
+
return "".join(out)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _policy_required() -> list:
|
|
81
|
+
"""读**真源**策略文件的必需正则(相对本模块定位,不依赖 cwd)。"""
|
|
82
|
+
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
83
|
+
path = os.path.join(root, "data", "policy.json")
|
|
84
|
+
with io.open(path, encoding="utf-8") as fh:
|
|
85
|
+
return [re.compile(p) for p in json.load(fh)["required"]]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> int:
|
|
89
|
+
print("== ① 两形态都判齐 ==")
|
|
90
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON), ("混形态", MIXED)):
|
|
91
|
+
comp = nodefile.ccg_completeness(body)
|
|
92
|
+
ok(comp["complete"] and sorted(comp["required_present"]) == sorted(MARKS),
|
|
93
|
+
"①%s:六要素判齐(present=%d/%d)" % (name, len(comp["required_present"]), len(MARKS)))
|
|
94
|
+
ok(abs(comp["ratio"] - 1.0) < 1e-9, "①%s:ratio=1.0(%s)" % (name, comp["ratio"]))
|
|
95
|
+
|
|
96
|
+
print("== ② 缺要素仍判不全(放宽的是标点,不是要求)==")
|
|
97
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON)):
|
|
98
|
+
for mark in MARKS:
|
|
99
|
+
broken = _drop(body, mark)
|
|
100
|
+
comp = nodefile.ccg_completeness(broken)
|
|
101
|
+
ok(not comp["complete"] and mark not in comp["required_present"],
|
|
102
|
+
"②%s 缺「%s」→ 判不全" % (name, mark))
|
|
103
|
+
ok(not nodefile.ccg_completeness("")["complete"], "②空正文 → 判不全")
|
|
104
|
+
ok(not nodefile.ccg_completeness("# 生效条件\n只要这一条\n")["complete"],
|
|
105
|
+
"②只声明一条 → 判不全(不冒充齐全)")
|
|
106
|
+
|
|
107
|
+
print("== ③ 写入闸门与检索面结论一致(读真源 policy 复算)==")
|
|
108
|
+
pats = _policy_required()
|
|
109
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON), ("混形态", MIXED),
|
|
110
|
+
("空正文", ""), ("只一条", "# 生效条件:x\n")):
|
|
111
|
+
gate = all(p.search(body) for p in pats)
|
|
112
|
+
retr = nodefile.ccg_completeness(body)["complete"]
|
|
113
|
+
ok(gate == retr, "③%s:闸门=%s 检索面=%s(必须相等)" % (name, gate, retr))
|
|
114
|
+
for mark in MARKS:
|
|
115
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON)):
|
|
116
|
+
broken = _drop(body, mark)
|
|
117
|
+
gate = all(p.search(broken) for p in pats)
|
|
118
|
+
retr = nodefile.ccg_completeness(broken)["complete"]
|
|
119
|
+
ok(gate == retr and not retr,
|
|
120
|
+
"③%s 缺「%s」:两侧同判不全" % (name, mark))
|
|
121
|
+
# 边界形态:二级标题 / 缩进标题 / 无空格 —— 两侧必须同判(宽松方向也要一致)。
|
|
122
|
+
# 注意按**该要素自身**的那条必需正则比对(拿「全六条」的结论比「单条」判据
|
|
123
|
+
# 是错的口径——空正文与二级标题会因两侧同假而蒙对,暴露不出真差异)。
|
|
124
|
+
for name, line in (("二级标题", "## 生效条件:x\n"),
|
|
125
|
+
("缩进标题", " # 生效条件:x\n"),
|
|
126
|
+
("无空格", "#生效条件:x\n"),
|
|
127
|
+
("制表符", "#\t生效条件:x\n")):
|
|
128
|
+
pat = next(p for p in pats if "生效条件" in p.pattern)
|
|
129
|
+
gate = bool(pat.search(line))
|
|
130
|
+
present = nodefile.ccg_mark_present(line, "生效条件")
|
|
131
|
+
ok(gate == present,
|
|
132
|
+
"③%s(%r):闸门=%s 判据=%s(必须相等)" % (name, line.strip(), gate, present))
|
|
133
|
+
# 全形态 × 全要素的逐条一致性(比「整体 complete」更细,能定位到具体要素)
|
|
134
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON), ("混形态", MIXED)):
|
|
135
|
+
for mark in MARKS:
|
|
136
|
+
pat = next(p for p in pats if mark in p.pattern)
|
|
137
|
+
ok(bool(pat.search(body)) == nodefile.ccg_mark_present(body, mark),
|
|
138
|
+
"③%s 逐要素一致:「%s」" % (name, mark))
|
|
139
|
+
|
|
140
|
+
print("== ④ 取值面:无冒号的值 = 标题后首个非空非标题行 ==")
|
|
141
|
+
ok(nodefile.ccg_field_value(COLON, "功能名") == "值_功能名",
|
|
142
|
+
"④带冒号:取行内值")
|
|
143
|
+
ok(nodefile.ccg_field_value(NONCOLON, "功能名") == "值_功能名",
|
|
144
|
+
"④无冒号:取下一行值")
|
|
145
|
+
ok(nodefile.ccg_field_value("# 执行\n\n真实值行\n后文\n", "执行") == "真实值行",
|
|
146
|
+
"④无冒号:跳过空行取首个非空行")
|
|
147
|
+
ok(nodefile.ccg_field_value("# 功能名\n# 生效条件:x\n", "功能名") == "",
|
|
148
|
+
"④无冒号且下一行又是标题:取空串而非吞掉下一个要素")
|
|
149
|
+
ok(nodefile.ccg_field_value(COLON, "不存在的字段") is None,
|
|
150
|
+
"④无该字段行 → None")
|
|
151
|
+
|
|
152
|
+
print("== ⑤ 四个副本与单点结论一致(防再长出口径分叉)==")
|
|
153
|
+
for name, body in (("带冒号", COLON), ("无冒号", NONCOLON)):
|
|
154
|
+
for mark in ("功能名", "生效条件", "执行"):
|
|
155
|
+
single = nodefile.ccg_mark_present(body, mark)
|
|
156
|
+
ok(ccgc._has_ccg_line(body, mark) == single,
|
|
157
|
+
"⑤ccgc._has_ccg_line(%s,%s) 与单点一致" % (name, mark))
|
|
158
|
+
ok(consolidate._has_ccg_line(body, mark) == single,
|
|
159
|
+
"⑤consolidate._has_ccg_line(%s,%s) 与单点一致" % (name, mark))
|
|
160
|
+
ok(bool(_ccg_field(body, mark)) == single or not single,
|
|
161
|
+
"⑤mdcos._ccg_field(%s,%s) 与单点一致" % (name, mark))
|
|
162
|
+
ok(tasks._field_line(body, mark) == (nodefile.ccg_field_value(body, mark) or ""),
|
|
163
|
+
"⑤tasks._field_line(%s,%s) 与单点同值" % (name, mark))
|
|
164
|
+
|
|
165
|
+
print("== ⑥ 写读闭环:无冒号节点 upsert 后取得到新值 ==")
|
|
166
|
+
for enc, upsert in (("ccgc", ccgc._upsert_ccg_line), ("consolidate", consolidate._upsert_ccg_line)):
|
|
167
|
+
after = upsert(NONCOLON, "生效条件", "新条件")
|
|
168
|
+
ok(_ccg_field(after, "生效条件") == "新条件",
|
|
169
|
+
"⑥%s:无冒号节点 upsert 后 mdcos 读出「新条件」" % enc)
|
|
170
|
+
ok(nodefile.ccg_field_value(after, "生效条件") == "新条件",
|
|
171
|
+
"⑥%s:同一次写单点读出「新条件」" % enc)
|
|
172
|
+
ok(nodefile.ccg_completeness(after)["complete"],
|
|
173
|
+
"⑥%s:upsert 后仍判齐" % enc)
|
|
174
|
+
try:
|
|
175
|
+
upsert(NONCOLON, "生效条件", "多行\n注入")
|
|
176
|
+
ok(False, "⑥%s:值含换行应被拒(N208)" % enc)
|
|
177
|
+
except ValueError:
|
|
178
|
+
ok(True, "⑥%s:值含换行被拒(N208 fail-closed)" % enc)
|
|
179
|
+
|
|
180
|
+
print()
|
|
181
|
+
print("ccg_form_parity: PASS=%d FAIL=%d" % (PASS, FAIL))
|
|
182
|
+
if FAILS:
|
|
183
|
+
print("FAILS: " + "; ".join(FAILS))
|
|
184
|
+
return 1 if FAIL else 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
sys.exit(main())
|
package/md_cg/test_ccgc.py
CHANGED
|
@@ -23,7 +23,7 @@ import tempfile
|
|
|
23
23
|
import threading
|
|
24
24
|
import time
|
|
25
25
|
|
|
26
|
-
from . import audit, backfill, ccgc, nodefile, units
|
|
26
|
+
from . import audit, backfill, ccgc, nodefile, tokens, units
|
|
27
27
|
from .mdcos import MdCGOS
|
|
28
28
|
|
|
29
29
|
PASS = FAIL = 0
|
|
@@ -126,6 +126,13 @@ def _import_mcp():
|
|
|
126
126
|
def main():
|
|
127
127
|
global PASS, FAIL
|
|
128
128
|
tmp = tempfile.mkdtemp(prefix="mdcg_ccgc_")
|
|
129
|
+
# N176 契约:落库准入须令牌验证方——本测试自备哑令牌环境(临时 MDCG_TOKEN_FILE)
|
|
130
|
+
_old_tf = os.environ.get(tokens.TOKEN_FILE_ENV)
|
|
131
|
+
os.environ[tokens.TOKEN_FILE_ENV] = os.path.join(tmp, "tokens.json")
|
|
132
|
+
tk_reviewer = tokens.issue("designer", actor="designer",
|
|
133
|
+
path=os.environ[tokens.TOKEN_FILE_ENV])["token"]
|
|
134
|
+
tk_reviewer_ext = tokens.issue("verify", actor="external-reviewer",
|
|
135
|
+
path=os.environ[tokens.TOKEN_FILE_ENV])["token"]
|
|
129
136
|
try:
|
|
130
137
|
root = os.path.join(tmp, "root")
|
|
131
138
|
os.makedirs(root, exist_ok=True)
|
|
@@ -192,8 +199,11 @@ def main():
|
|
|
192
199
|
a_defer = ccgc.attest("tgt", ccgc.DEFER, "designer", "agent:a1", cg=cg)
|
|
193
200
|
ok((not a_defer.ok) and "E042" in a_defer.error, "V7b DEFER 不构成签章")
|
|
194
201
|
a_ok = ccgc.attest("tgt", ccgc.ACCEPT, "designer", "agent:a1",
|
|
195
|
-
evidence="设计者核对原文一致", cg=cg
|
|
202
|
+
evidence="设计者核对原文一致", cg=cg,
|
|
203
|
+
verifier_token=tk_reviewer)
|
|
196
204
|
ok(a_ok.ok and bool(a_ok.token), "V7c 编外验证方 ACCEPT → 有效签章")
|
|
205
|
+
ok(a_ok.verifier_identity == "token", "V7d 令牌验证方 identity=token"
|
|
206
|
+
"(N176:self-reported 不再构成落库准入)")
|
|
197
207
|
|
|
198
208
|
# ---------- V8 无章不写 ----------
|
|
199
209
|
l0 = ccgc.link(r, None, cg=cg, apply=True)
|
|
@@ -283,7 +293,7 @@ def main():
|
|
|
283
293
|
lz = ccgc.link_pending(cg, "tgt", apply=True, actor="agent:a1")
|
|
284
294
|
ok(lz.ok and lz.written == 6, "V16f 读回原件+签章 → 落库成功")
|
|
285
295
|
ok(not os.path.isfile(sv["path"]), "V16g 落库成功即清 pending(暂存非存档)")
|
|
286
|
-
ok(not any(ccgc.PENDING_DIR in str(k) for k in (cg.index.get("nodes") or {})),
|
|
296
|
+
ok(not any(ccgc.PENDING_DIR in str(k) for k in (list(cg.index.get("nodes") or {}))),
|
|
287
297
|
"V16h pending 不进索引(冷区对检索隐身)")
|
|
288
298
|
|
|
289
299
|
# ---------- V17 闸门:只认编外裁决,绝不假装通过 ----------
|
|
@@ -396,6 +406,17 @@ def main():
|
|
|
396
406
|
ok(c5["state"] == units.HIVE and c5["verdict"] == "ACCEPT" and c5["passed"] is True,
|
|
397
407
|
"V20g review 取回裁决:ok/verdict/passed 三者分离")
|
|
398
408
|
ok(bool((c5.get("attest") or {}).get("ok")), "V20h 拿到裁决即自动签章")
|
|
409
|
+
c5b = mcp._ccg_call(cg, {"ccg": {"action": "link", "node_id": "tgt",
|
|
410
|
+
"apply": True}})
|
|
411
|
+
ok((not c5b["ok"]) and "E052" in str((c5b.get("link") or {}).get("errors")),
|
|
412
|
+
"V20n 无令牌自报签章 → link 拒绝(N176 E052,不落 knowledge 层)")
|
|
413
|
+
c5c = mcp._ccg_call(cg, {"ccg": {"action": "attest", "node_id": "tgt",
|
|
414
|
+
"verdict": "ACCEPT",
|
|
415
|
+
"verifier": "external-reviewer",
|
|
416
|
+
"verifier_token": tk_reviewer_ext,
|
|
417
|
+
"evidence": "令牌验证方复核"}})
|
|
418
|
+
ok(c5c["ok"] and (c5c.get("attest") or {}).get("verifier_identity") == "token",
|
|
419
|
+
"V20o 令牌验证方重签章 → identity=token(挂回 pending 待落库)")
|
|
399
420
|
c7 = mcp._ccg_call(cg, {"ccg": {"action": "review", "node_id": "tgt",
|
|
400
421
|
"jobs": jobs, "blocking": False}})
|
|
401
422
|
ok(c7["state"] == units.HIVE and c7["verdict"] is None and c7["passed"] is False,
|
|
@@ -421,6 +442,10 @@ def main():
|
|
|
421
442
|
os.environ[units.ENV_EXE] = _old_exe
|
|
422
443
|
|
|
423
444
|
finally:
|
|
445
|
+
if _old_tf is None:
|
|
446
|
+
os.environ.pop(tokens.TOKEN_FILE_ENV, None)
|
|
447
|
+
else:
|
|
448
|
+
os.environ[tokens.TOKEN_FILE_ENV] = _old_tf
|
|
424
449
|
print()
|
|
425
450
|
print("ccgc: PASS=%d FAIL=%d" % (PASS, FAIL))
|
|
426
451
|
if FAILS:
|
|
@@ -33,7 +33,7 @@ import shutil
|
|
|
33
33
|
import sys
|
|
34
34
|
import tempfile
|
|
35
35
|
|
|
36
|
-
from . import ccgc, consolidate, crosscheck, vision_evidence
|
|
36
|
+
from . import ccgc, consolidate, crosscheck, tokens, vision_evidence
|
|
37
37
|
from .mdcos import MdCGOS
|
|
38
38
|
|
|
39
39
|
PASS = FAIL = 0
|
|
@@ -181,9 +181,21 @@ def g2_link(tmp):
|
|
|
181
181
|
r = ccgc.compile_dialog(DIALOG, "gd_link", "agent:a1",
|
|
182
182
|
slots=SLOTS, marks=MARKS, cg=a)
|
|
183
183
|
ok(r.success, "G2a 编译成功")
|
|
184
|
+
# N176 契约:落库准入须令牌验证方——哑令牌临时文件 + 临时 MDCG_TOKEN_FILE
|
|
185
|
+
# (attest→verify_token 走环境变量定位令牌库,绝不触真实令牌库)
|
|
186
|
+
tok_env = os.path.join(tmp, "g2_tokens.json")
|
|
187
|
+
_old_tf = os.environ.get(tokens.TOKEN_FILE_ENV)
|
|
188
|
+
os.environ[tokens.TOKEN_FILE_ENV] = tok_env
|
|
189
|
+
tk = tokens.issue("designer", actor="designer", path=tok_env)["token"]
|
|
184
190
|
a_ok = ccgc.attest("gd_link", ccgc.ACCEPT, "designer", "agent:a1",
|
|
185
|
-
evidence="守卫:候选与对话原文一致", cg=a
|
|
186
|
-
|
|
191
|
+
evidence="守卫:候选与对话原文一致", cg=a,
|
|
192
|
+
verifier_token=tk)
|
|
193
|
+
if _old_tf is None:
|
|
194
|
+
os.environ.pop(tokens.TOKEN_FILE_ENV, None)
|
|
195
|
+
else:
|
|
196
|
+
os.environ[tokens.TOKEN_FILE_ENV] = _old_tf
|
|
197
|
+
ok(a_ok.ok and a_ok.verifier_identity == "token", "G2b 编外验证方签章"
|
|
198
|
+
"(N176:令牌凭据,self-reported 不构成落库准入)")
|
|
187
199
|
l = ccgc.link(r, a_ok, cg=a, apply=True)
|
|
188
200
|
ok(l.ok and l.written == 6, "G2c link 写入六行")
|
|
189
201
|
fm, _c = _disk(root, "gd_link")
|
|
@@ -308,7 +320,8 @@ def _g89_root(tmp):
|
|
|
308
320
|
a = MdCGOS(root)
|
|
309
321
|
a.add("gd_pro",
|
|
310
322
|
"# 功能名:待提升节点\n# 生效条件:问提升\n# 子功能:说明提升\n"
|
|
311
|
-
"# 执行:按热度\n#
|
|
323
|
+
"# 执行:按热度\n# 验证方式:编译器/静态检查通过\n"
|
|
324
|
+
"# 不适用条件:问无关\n",
|
|
312
325
|
layer="contextual", merge_count=2)
|
|
313
326
|
a.flush()
|
|
314
327
|
return root
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · H2:会话视图的读侧归一(「写进去能查出来」)
|
|
3
|
+
|
|
4
|
+
病灶(端到端实测):写侧落盘值 = `_normalize_session(请求声明值)`
|
|
5
|
+
(`MdCGSecure._attribution` 取 `cg.session`,而 `cg.session` 由 `call_tool`
|
|
6
|
+
→ `_declared_session` 归一);读侧 `stg.timeline` 却拿**未归一的原值**做
|
|
7
|
+
等值比较 ⇒ 同一条记忆「写进去查不出」——DSH 形态的会话 id 在会话根下不
|
|
8
|
+
存在时写侧落 `anonymous`,读侧按 `session-…` 精确匹配得 `count=0`。
|
|
9
|
+
|
|
10
|
+
修复(一处归一,改在视图层):`stg._view_session` 只对「具体会话值」这一态
|
|
11
|
+
消费 `mcp_server._normalize_session`(真源唯一,**不重写实现**),三态语义
|
|
12
|
+
(None / "" / "*" = 跨会话)逐位不变。
|
|
13
|
+
|
|
14
|
+
覆盖
|
|
15
|
+
----
|
|
16
|
+
A 端到端:MCP 写一条声明了 DSH 形态 session 的记忆 → 落盘值是归一值,
|
|
17
|
+
再用**原始声明值**读回来必须命中(H2 的核心断言)
|
|
18
|
+
B 归一真源唯一:读侧消费的确实是 `mcp_server._normalize_session`
|
|
19
|
+
(哨兵替换法——防后续有人在 stg 里重写一把尺,那必然造出第三种不等值)
|
|
20
|
+
C 三态视图不回归:None / "" / "*" 仍跨会话;具体非 DSH 值仍精确匹配
|
|
21
|
+
D fail-soft:会话根不可读(OSError)时读写两侧都保原值,仍能读回
|
|
22
|
+
E 不越界:cg 侧读路径(search)不吃请求 session(issue #35 定稿:身份不可自报)
|
|
23
|
+
|
|
24
|
+
运行:python -m md_cg.test_h2_session_view_norm
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import shutil
|
|
30
|
+
import sys
|
|
31
|
+
import tempfile
|
|
32
|
+
|
|
33
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
34
|
+
|
|
35
|
+
# Windows cmd 默认 GBK:中文打印即 UnicodeEncodeError,且崩在断言之后、报告之前。
|
|
36
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
37
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
38
|
+
|
|
39
|
+
from md_cg.mdcos import MdCGSecure
|
|
40
|
+
from md_cg.security import DEFAULT_SENSITIVITY, Principal
|
|
41
|
+
from md_cg import mcp_server as ms
|
|
42
|
+
|
|
43
|
+
PASS = 0
|
|
44
|
+
FAILS = []
|
|
45
|
+
|
|
46
|
+
# 合成会话 id(uuid4 形态的 DSH 会话;非真实凭据,仅形态样本)
|
|
47
|
+
DSH_ID = "session-11111111-2222-3333-4444-555555555555"
|
|
48
|
+
ENV_KEY = "MDCG_DSH_SESSIONS_ROOT"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check(name, cond, detail=""):
|
|
52
|
+
global PASS
|
|
53
|
+
if cond:
|
|
54
|
+
PASS += 1
|
|
55
|
+
print(f" [PASS] {name}" + (f" · {detail}" if detail else ""))
|
|
56
|
+
else:
|
|
57
|
+
FAILS.append(name)
|
|
58
|
+
print(f" [FAIL] {name} · {str(detail)[:240]}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _p(actor, session):
|
|
62
|
+
return Principal(actor=actor, clearance=DEFAULT_SENSITIVITY,
|
|
63
|
+
can_write=True, can_admin=True, role="designer",
|
|
64
|
+
session=session, harness="test-harness")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _remember(cg, text, session):
|
|
68
|
+
"""经 MCP 写入口落一条(gated 路径,与插件自动记忆同一条链)。"""
|
|
69
|
+
return ms.call_tool(cg, "mdcg_remember", {
|
|
70
|
+
"content": text, "layer": "contextual", "session": session,
|
|
71
|
+
"gated": True, "importance": 0.6})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _timeline(cg, session):
|
|
75
|
+
return ms.call_tool(cg, "stg", {"op": "timeline", "limit": 20, "session": session})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_a(root, sessions_root):
|
|
79
|
+
"""A 写进去能查出来(核心断言)。"""
|
|
80
|
+
print("\n[A] 端到端:DSH 形态声明 → 落盘归一值 → 原值读回命中")
|
|
81
|
+
old = os.environ.get(ENV_KEY)
|
|
82
|
+
os.environ[ENV_KEY] = sessions_root # 根可读、但无该会话
|
|
83
|
+
try:
|
|
84
|
+
# 前置条件断言:本用例的环境确实触发归一,否则下面的断言是空的
|
|
85
|
+
check("A0 前置:该环境下 DSH 形态归一为 anonymous",
|
|
86
|
+
ms._normalize_session(DSH_ID) == "anonymous",
|
|
87
|
+
ms._normalize_session(DSH_ID))
|
|
88
|
+
cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
|
|
89
|
+
res = _remember(cg, "阿尔法 会话视图归一 端到端写入样本", DSH_ID)
|
|
90
|
+
nid = res.get("written") or res.get("node_id")
|
|
91
|
+
check("A1 写入落盘(ACCEPT 且有节点 id)",
|
|
92
|
+
bool(nid) and res.get("verdict") == "ACCEPT",
|
|
93
|
+
(res.get("verdict"), nid))
|
|
94
|
+
fm = (cg.get(nid) or {}).get("frontmatter") or {}
|
|
95
|
+
check("A2 落盘 session = 归一声明值(写侧口径)",
|
|
96
|
+
fm.get("session") == "anonymous", fm.get("session"))
|
|
97
|
+
tl = _timeline(cg, DSH_ID)
|
|
98
|
+
check("A3 用**原始声明值**读回命中(H2 核心:写进去查得出)",
|
|
99
|
+
tl.get("count") == 1 and [i["id"] for i in tl.get("items", [])] == [nid],
|
|
100
|
+
(tl.get("count"), [i["id"] for i in tl.get("items", [])[:3]]))
|
|
101
|
+
check("A4 返回体 session = 归一后的生效值(诚实回带)",
|
|
102
|
+
tl.get("session") == "anonymous", tl.get("session"))
|
|
103
|
+
finally:
|
|
104
|
+
if old is None:
|
|
105
|
+
os.environ.pop(ENV_KEY, None)
|
|
106
|
+
else:
|
|
107
|
+
os.environ[ENV_KEY] = old
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_b(root, sessions_root):
|
|
111
|
+
"""B 归一真源唯一(哨兵法)。"""
|
|
112
|
+
print("\n[B] 读侧消费 mcp_server 真源(不重写一把尺)")
|
|
113
|
+
old = os.environ.get(ENV_KEY)
|
|
114
|
+
os.environ[ENV_KEY] = sessions_root
|
|
115
|
+
orig = ms._normalize_session
|
|
116
|
+
try:
|
|
117
|
+
cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
|
|
118
|
+
ms._normalize_session = lambda raw: "SENTINEL_" + str(raw)
|
|
119
|
+
out = _timeline(cg, "probe")
|
|
120
|
+
check("B1 具体值经 mcp_server._normalize_session(哨兵可见)",
|
|
121
|
+
out.get("session") == "SENTINEL_probe", out.get("session"))
|
|
122
|
+
star = _timeline(cg, "*")
|
|
123
|
+
check("B2 \"*\" 不经归一(跨会话开关是不变量)",
|
|
124
|
+
star.get("session") is None, star.get("session"))
|
|
125
|
+
blank = _timeline(cg, "")
|
|
126
|
+
check("B3 空串不经归一(缺省语义不变量)",
|
|
127
|
+
blank.get("session") is None, blank.get("session"))
|
|
128
|
+
finally:
|
|
129
|
+
ms._normalize_session = orig
|
|
130
|
+
if old is None:
|
|
131
|
+
os.environ.pop(ENV_KEY, None)
|
|
132
|
+
else:
|
|
133
|
+
os.environ[ENV_KEY] = old
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_c(root, sessions_root):
|
|
137
|
+
"""C 三态视图不回归 + 具体值精确匹配。"""
|
|
138
|
+
print("\n[C] 视图三态与精确匹配不回归")
|
|
139
|
+
old = os.environ.get(ENV_KEY)
|
|
140
|
+
os.environ[ENV_KEY] = sessions_root
|
|
141
|
+
try:
|
|
142
|
+
cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
|
|
143
|
+
_remember(cg, "贝塔 属于 会话 sess_A 的知识点", "sess_A")
|
|
144
|
+
a = _timeline(cg, "sess_A")
|
|
145
|
+
check("C1 具体非 DSH 值精确匹配(原样采用)",
|
|
146
|
+
a.get("count") == 1 and a.get("session") == "sess_A",
|
|
147
|
+
(a.get("count"), a.get("session")))
|
|
148
|
+
z = _timeline(cg, "sess_Z")
|
|
149
|
+
check("C2 未知会话为空(不因缺省而放行)",
|
|
150
|
+
z.get("count") == 0, z.get("count"))
|
|
151
|
+
for label, val in (("缺省", None), ("空串", ""), ("星号", "*")):
|
|
152
|
+
out = _timeline(cg, val)
|
|
153
|
+
check(f"C3 {label}仍为跨会话视图(返回体 session=None 且非空)",
|
|
154
|
+
out.get("session") is None and out.get("count") >= 1,
|
|
155
|
+
(out.get("session"), out.get("count")))
|
|
156
|
+
finally:
|
|
157
|
+
if old is None:
|
|
158
|
+
os.environ.pop(ENV_KEY, None)
|
|
159
|
+
else:
|
|
160
|
+
os.environ[ENV_KEY] = old
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_d(root, tmp):
|
|
164
|
+
"""D fail-soft:根不可读 → 两侧都保原值。"""
|
|
165
|
+
print("\n[D] 会话根不可读(OSError)时 fail-soft 读写一致")
|
|
166
|
+
old = os.environ.get(ENV_KEY)
|
|
167
|
+
os.environ[ENV_KEY] = os.path.join(tmp, "no_such_root_dir")
|
|
168
|
+
try:
|
|
169
|
+
check("D1 根不可读 → 归一保原值",
|
|
170
|
+
ms._normalize_session(DSH_ID) == DSH_ID,
|
|
171
|
+
ms._normalize_session(DSH_ID))
|
|
172
|
+
cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
|
|
173
|
+
res = _remember(cg, "伽马 根不可读 场景写入样本", DSH_ID)
|
|
174
|
+
nid = res.get("written") or res.get("node_id")
|
|
175
|
+
fm = (cg.get(nid) or {}).get("frontmatter") or {}
|
|
176
|
+
check("D2 落盘保原值(不因环境差异丢会话标记)",
|
|
177
|
+
fm.get("session") == DSH_ID, fm.get("session"))
|
|
178
|
+
tl = _timeline(cg, DSH_ID)
|
|
179
|
+
check("D3 同环境下读回命中(读写两侧同尺)",
|
|
180
|
+
tl.get("count") == 1, tl.get("count"))
|
|
181
|
+
finally:
|
|
182
|
+
if old is None:
|
|
183
|
+
os.environ.pop(ENV_KEY, None)
|
|
184
|
+
else:
|
|
185
|
+
os.environ[ENV_KEY] = old
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_e(root, sessions_root):
|
|
189
|
+
"""E 不越界:cg 侧读路径不吃请求 session(issue #35 定稿)。"""
|
|
190
|
+
print("\n[E] 不越界:search 的 session 不构成授权/过滤豁免")
|
|
191
|
+
old = os.environ.get(ENV_KEY)
|
|
192
|
+
os.environ[ENV_KEY] = sessions_root
|
|
193
|
+
try:
|
|
194
|
+
cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
|
|
195
|
+
_remember(cg, "德尔塔 共享档检索样本", "sess_A")
|
|
196
|
+
res_default, _ = cg.search("德尔塔", judge=False, record=False)
|
|
197
|
+
res_dsh, _ = cg.search("德尔塔", session=DSH_ID, judge=False, record=False)
|
|
198
|
+
ids_default = {r[0].get("id") if isinstance(r, tuple) else r.get("id")
|
|
199
|
+
for r in res_default}
|
|
200
|
+
ids_dsh = {r[0].get("id") if isinstance(r, tuple) else r.get("id")
|
|
201
|
+
for r in res_dsh}
|
|
202
|
+
check("E1 传 DSH 形态 session 与缺省结果一致(cg 侧不经 _view_session)",
|
|
203
|
+
ids_default == ids_dsh and bool(ids_default),
|
|
204
|
+
(sorted(ids_default)[:3], sorted(ids_dsh)[:3]))
|
|
205
|
+
finally:
|
|
206
|
+
if old is None:
|
|
207
|
+
os.environ.pop(ENV_KEY, None)
|
|
208
|
+
else:
|
|
209
|
+
os.environ[ENV_KEY] = old
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main():
|
|
213
|
+
tmp = tempfile.mkdtemp(prefix="mdcg_h2_")
|
|
214
|
+
sessions_root = os.path.join(tmp, "sessions")
|
|
215
|
+
os.makedirs(sessions_root, exist_ok=True)
|
|
216
|
+
try:
|
|
217
|
+
test_a(os.path.join(tmp, "root_a"), sessions_root)
|
|
218
|
+
test_b(os.path.join(tmp, "root_b"), sessions_root)
|
|
219
|
+
test_c(os.path.join(tmp, "root_c"), sessions_root)
|
|
220
|
+
test_d(os.path.join(tmp, "root_d"), tmp)
|
|
221
|
+
test_e(os.path.join(tmp, "root_e"), sessions_root)
|
|
222
|
+
finally:
|
|
223
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
224
|
+
|
|
225
|
+
print("\n通过 %d / 失败 %d" % (PASS, len(FAILS)))
|
|
226
|
+
if FAILS:
|
|
227
|
+
print("失败项:" + ", ".join(FAILS))
|
|
228
|
+
return 1
|
|
229
|
+
return 0
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if __name__ == "__main__":
|
|
233
|
+
sys.exit(main())
|