@furongjun1999/dsh-memory 0.6.1 → 0.7.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 +49 -18
- package/docs/README.md +1 -0
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +223 -0
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.1.md +340 -0
- package/docs/eval/issue50_/345/205/203/346/225/260/346/215/256/351/200/217/344/274/240/344/270/216/345/205/234/345/272/225_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +437 -0
- package/docs/eval/issue50_/345/215/212/351/207/215/345/244/215/345/276/205/345/256/232/345/244/215/346/240/270_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +488 -0
- package/docs/eval/issue50_/345/276/205/345/256/232/345/244/215/346/240/270/345/205/245/351/230/237_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +428 -0
- package/docs/eval/issue50_/350/257/273/351/235/242/344/277/235/346/212/244/345/217/252/350/256/244/346/230/276/345/274/217/346/235/245/346/272/220_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +271 -0
- package/docs/eval/issue50_/351/207/215/350/246/201/345/272/246/345/220/214/346/272/220/344/270/216/344/277/235/346/212/244/350/257/255/344/271/211_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +306 -0
- package/docs/eval/issue51_/344/270/200/351/224/256/345/256/211/350/243/205/345/244/261/350/264/245_/345/275/222/345/261/236/345/210/244/345/256/232_v1.0.md +58 -0
- package/docs/eval/issue52_/346/235/241/344/273/266/345/205/210/350/241/214/344/270/216/346/210/252/346/226/255/345/217/257/350/247/202/346/265/213_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +360 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/241/346/241/243/344/275/215/345/215/225/344/270/200/345/205/245/345/217/243_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +651 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/242/345/217/230/346/233/264/345/215/225/344/270/216/345/233/236/346/273/232/345/216/237/350/257/255_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +439 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/243/345/207/206/345/205/245/350/257/273/346/225/260_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1337 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/244/346/224/266/345/256/230/344/270/216/345/205/250/351/223/276/351/252/214/346/224/266_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1817 -0
- package/docs/eval//345/207/272/350/264/247/351/235/242/345/206/222/347/203/237_/350/277/233/350/264/247/351/227/250/347/246/201_v1.0.md +460 -0
- package/docs/eval//345/217/221/345/270/20308_/350/207/252/350/277/255/344/273/243/344/270/216/347/235/241/347/234/240_/345/233/276/346/243/200/347/264/242/350/267/257/344/270/216/346/235/203/351/207/215_v1.0.md +97 -0
- package/docs/eval//345/217/221/345/270/20309_/346/243/200/347/264/242/351/235/242/344/270/211/346/211/271/346/224/266/345/217/243_v1.0.md +66 -0
- package/docs/eval//345/275/222/344/270/200/345/261/202/347/274/272/347/234/201/347/277/273/345/205/263_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +458 -0
- package/docs/eval//347/254/2543/345/261/202stg/347/273/223/346/236/204/347/264/242/345/274/225_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +250 -0
- package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +136 -11
- 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 +32 -2
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +88 -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 +47 -11
- package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +183 -0
- package/docs/plans/stg/346/235/241/344/273/266/345/214/226/344/270/216/347/273/223/346/236/204/347/264/242/345/274/225_/350/256/276/350/256/241_v0.1.md +124 -0
- package/docs/plans//347/235/241/347/234/240/344/270/216/350/207/252/350/277/255/344/273/243_/345/212/237/350/203/275/344/274/230/345/214/226/350/256/276/350/256/241_v0.4.md +547 -0
- package/docs/plans//350/256/260/345/277/206/350/207/252/345/244/204/347/220/206/344/270/211/346/241/243/350/207/252/346/262/273_/350/256/276/350/256/241_v0.2.md +207 -0
- package/md_cg/admission.py +718 -0
- package/md_cg/autonomy_modes.py +642 -0
- package/md_cg/bench_e2e_locomo_qa.py +11 -4
- package/md_cg/chain.py +47 -0
- package/md_cg/consistency.py +133 -8
- package/md_cg/forgetting.py +38 -6
- package/md_cg/freshness.py +527 -0
- package/md_cg/generation.py +409 -0
- package/md_cg/hotcache.py +4 -1
- package/md_cg/lifecycle.py +30 -2
- package/md_cg/mcp_server.py +191 -26
- package/md_cg/mdcg.py +594 -39
- package/md_cg/mdcos.py +869 -38
- package/md_cg/nodefile.py +74 -1
- package/md_cg/protect.py +79 -4
- package/md_cg/provenance.py +1 -1
- package/md_cg/review_cli.py +31 -2
- package/md_cg/rollback.py +411 -0
- package/md_cg/rollback_cli.py +104 -0
- package/md_cg/semantic/canonical.py +22 -0
- package/md_cg/semantic/unify.py +73 -22
- package/md_cg/semantic/unify_fixture.json +25 -0
- package/md_cg/sleep.py +1297 -0
- package/md_cg/stg.py +400 -50
- package/md_cg/stgidx.py +281 -0
- package/md_cg/sustain.py +146 -9
- package/md_cg/test_auto_defaults.py +424 -0
- package/md_cg/test_autonomy_admission.py +1098 -0
- package/md_cg/test_autonomy_modes.py +1972 -0
- package/md_cg/test_b1_auto_id_multiproc.py +7 -0
- package/md_cg/test_b1b2_write_face.py +7 -0
- package/md_cg/test_b3_merge_keeps_content.py +7 -0
- package/md_cg/test_boundary_hit.py +410 -0
- package/md_cg/test_cons200_scan_selection.py +791 -0
- package/md_cg/test_en_pipeline.py +22 -13
- package/md_cg/test_generation_guard.py +352 -0
- package/md_cg/test_h4_sustain_snapshot.py +14 -4
- package/md_cg/test_i50a_half_dup_defer.py +408 -0
- package/md_cg/test_i50b_defer_to_review_queue.py +545 -0
- package/md_cg/test_i50c_meta_passthrough.py +608 -0
- package/md_cg/test_i50d_importance_source.py +531 -0
- package/md_cg/test_i50e_readside_protection.py +656 -0
- package/md_cg/test_issue39_utf8_stdio.py +9 -1
- package/md_cg/test_issue52_scan_condition_first.py +677 -0
- package/md_cg/test_linkref.py +7 -0
- package/md_cg/test_mode_parity.py +1393 -0
- package/md_cg/test_mutation_rollback.py +805 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +1 -1
- package/md_cg/test_n212_n213_n224_generation_gates.py +14 -8
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +7 -0
- package/md_cg/test_n230_dirty_replay.py +375 -0
- package/md_cg/test_p2_mcp.py +8 -0
- package/md_cg/test_p2_six_elements.py +463 -0
- package/md_cg/test_p3_legacy_closure.py +433 -0
- package/md_cg/test_p4_freshness.py +680 -0
- package/md_cg/test_p8_subgraph_chain.py +14 -1
- package/md_cg/test_p9_forget_protect.py +7 -0
- package/md_cg/test_p9c_dedup_hints.py +7 -0
- package/md_cg/test_policy_required_ccg.py +5 -1
- package/md_cg/test_protocol.py +7 -0
- package/md_cg/test_rank_parity_score_mode.py +6 -0
- package/md_cg/test_semantic_canonical.py +5 -3
- package/md_cg/test_sleep.py +611 -0
- package/md_cg/test_sleep_p1.py +784 -0
- package/md_cg/test_stgidx_index_parity.py +1020 -0
- package/md_cg/test_time_core_lint.py +968 -0
- package/md_cg/test_unify_default_off.py +701 -0
- package/md_cg/test_unify_scope.py +182 -0
- package/md_cg/test_writelimit.py +36 -9
- package/md_cg/test_writepipe.py +5 -2
- package/md_cg/weights.py +15 -1
- package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
- package/md_cg/writepipe.py +101 -2
- package/package.json +3 -2
- package/skills/plugin.json +1 -1
- package/utf8_boot.py +237 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""P2 守卫 · 六要素 6 行索引(§5.1)/ 拒绝域双语义(§5.2)/ 全进默认检索(§5.4)。
|
|
3
|
+
|
|
4
|
+
覆盖判据(每条对应契约的一项):
|
|
5
|
+
|
|
6
|
+
G1 **六要素那张表从 4 行长成 6 行**:`nodefile.CCG_INDEX_ROLES` 恰 6 行、
|
|
7
|
+
前 4 行 == 理论 `智能论3.4.md:3053-3059` 的 4 行、后 2 行 == 新增的
|
|
8
|
+
「验证方式(后置条件词)/ 不适用条件(拒绝域词)」;6 行字段名集合与
|
|
9
|
+
`nodefile.CCG_MARKS` **同源**(不新造第六个要素名)。
|
|
10
|
+
G2 **取要素文本走既有单点**:`ccg_element_terms` 吃的是
|
|
11
|
+
`ccg_field_value` 的行语义(冒号可有可无、后接行形态),**不是**检索面
|
|
12
|
+
自写的正则——三条行为断言(冒号形态 / 无冒号换行形态 / 缺行)+
|
|
13
|
+
与 `ccg_field_value` 的对拍;并断言词长下限与 `mdcg.NEG_MIN_TERM` 同值。
|
|
14
|
+
G3 **两个键进索引条目**(`_node_entry`,与 `time_window`/`observation_position`
|
|
15
|
+
同为免读文件的扁指标量):条目含两键且值 == nodefile 单点计算值;
|
|
16
|
+
**写路径(`add`)与重建路径(`rebuild_index`)同口径**。
|
|
17
|
+
G4 **重建协议四条硬约束**(§5.3):①幂等(`_fingerprint` 对拍 + 两次重建的
|
|
18
|
+
`_index.json` 字节相同)②增量优先(指纹未变时 `_maybe_reload_index`
|
|
19
|
+
不换索引对象)③可回退(源面 .md 逐字节不变)④可对拍(重建前后同查询
|
|
20
|
+
top-k 序列与分数**逐位不变**)。
|
|
21
|
+
G5 **拒绝域双语义**(§5.2,前置必做):**同一节点**既被边界命中、
|
|
22
|
+
又**未**被资格 REJECT;`boundary_hit` 与资格 REJECT **分开计数**
|
|
23
|
+
(两个独立键);灵敏度不变(情境真的落在拒绝域 ⇒ 非边界问句照旧 REJECT)。
|
|
24
|
+
G6 **全进默认检索**(§5.4):默认 `search_rrf` 上,边界问句只召回「声明了
|
|
25
|
+
拒绝域」的节点并打 `boundary_hit`;拒绝域词**不作普通问句的召回键**
|
|
26
|
+
(`positive_body` 的既有立场不变);`meta["boundary"]` 与
|
|
27
|
+
`meta["judge_filtered"]` 分开呈现。
|
|
28
|
+
G7 **旧库零破坏**:条目缺这两键(未重建的旧索引快照)时 `index_key_hits`
|
|
29
|
+
判 False;`_like` 的三参旧签名行为与改动前一致。
|
|
30
|
+
|
|
31
|
+
运行:
|
|
32
|
+
python -X utf8 -m md_cg.test_p2_six_elements # 正向
|
|
33
|
+
python -X utf8 -m md_cg.test_p2_six_elements --mutate # 定点变异自证
|
|
34
|
+
python -X utf8 -m md_cg.test_p2_six_elements --mutate --list # 列出变异
|
|
35
|
+
|
|
36
|
+
退出码:0 = 全绿;1 = 有断言失败 / 变异未按预期转红;2 = ANCHOR-MISS
|
|
37
|
+
(变异锚点在本源码里找不到——实现改了却没同步本表,硬失败不静默跳过)。
|
|
38
|
+
|
|
39
|
+
硬边界:不动任何在役数据根(库根一律 tempfile);不 git add/commit/push。
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import hashlib
|
|
44
|
+
import json
|
|
45
|
+
import os
|
|
46
|
+
import sys
|
|
47
|
+
import tempfile
|
|
48
|
+
|
|
49
|
+
# ── 隔离前置:必须在 import md_cg 子模块之前(tokens/theory 等模块级冻 aux_root)──
|
|
50
|
+
_TMP0 = tempfile.mkdtemp(prefix="p2_six_")
|
|
51
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP0, "auxroot")
|
|
52
|
+
os.environ["MDCG_MASTER_KEY"] = "ef" * 32
|
|
53
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP0, "tokens.json")
|
|
54
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP0, "cgroot")
|
|
55
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
56
|
+
"MDCG_RETRIEVAL_PIPELINE", "MDCG_POOLING", "MDCG_FRESHNESS"):
|
|
57
|
+
os.environ.pop(_k, None)
|
|
58
|
+
|
|
59
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
63
|
+
except AttributeError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
from md_cg import nodefile # noqa: E402
|
|
67
|
+
from md_cg import mdcg as M # noqa: E402
|
|
68
|
+
from md_cg.mdcos import MdCGOS # noqa: E402
|
|
69
|
+
|
|
70
|
+
PASS = 0
|
|
71
|
+
FAIL = 0
|
|
72
|
+
FAILS = []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def check(name, cond, detail=""):
|
|
76
|
+
global PASS, FAIL
|
|
77
|
+
if cond:
|
|
78
|
+
PASS += 1
|
|
79
|
+
print(f" OK {name}")
|
|
80
|
+
else:
|
|
81
|
+
FAIL += 1
|
|
82
|
+
FAILS.append(name)
|
|
83
|
+
print(f" FAIL {name} {detail}")
|
|
84
|
+
return bool(cond)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
BODY = ("# 功能名:{n}\n"
|
|
88
|
+
"# 生效条件:无条件\n"
|
|
89
|
+
"# 子功能:无\n"
|
|
90
|
+
"# 执行:占位\n"
|
|
91
|
+
"# 验证方式:compiler test 复现\n"
|
|
92
|
+
"# 不适用条件:{neg}\n\n{t}\n")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
#: 不适用条件按 `;` 分条(与写入面 ccgc 的拆分口径同源:每条=一个独立拒绝域条目)
|
|
96
|
+
NEG_TWO = "其它会话;无关查询"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _mk(cg, nid, neg=NEG_TWO, tail=None, **kw):
|
|
100
|
+
cg.add(nid, BODY.format(n=nid, neg=neg, t=tail or f"{nid} 正文 苹果 章节"),
|
|
101
|
+
layer="knowledge", **kw)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _tree_hash(root):
|
|
105
|
+
h = hashlib.sha256()
|
|
106
|
+
for dirpath, dirs, files in os.walk(root):
|
|
107
|
+
# 派生物/运行态(_index.json / _index_log/ / _access.log …)不入源面对拍
|
|
108
|
+
dirs[:] = sorted(d for d in dirs if not d.startswith("_"))
|
|
109
|
+
for fn in sorted(files):
|
|
110
|
+
if fn.startswith("_"):
|
|
111
|
+
continue
|
|
112
|
+
p = os.path.join(dirpath, fn)
|
|
113
|
+
h.update(os.path.relpath(p, root).replace("\\", "/").encode())
|
|
114
|
+
with open(p, "rb") as f:
|
|
115
|
+
h.update(f.read())
|
|
116
|
+
return h.hexdigest()[:16]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _corpus(nd=2):
|
|
120
|
+
root = tempfile.mkdtemp(prefix="p2_six_lib_")
|
|
121
|
+
cg = MdCGOS(root)
|
|
122
|
+
if nd >= 1:
|
|
123
|
+
_mk(cg, "k_decl")
|
|
124
|
+
if nd >= 2:
|
|
125
|
+
_mk(cg, "k_nodecl", neg="无")
|
|
126
|
+
cg.flush()
|
|
127
|
+
return root, cg
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def g1_table_six_rows():
|
|
131
|
+
print("== G1 六要素表:4 行 → 6 行 ==")
|
|
132
|
+
roles = tuple(nodefile.CCG_INDEX_ROLES)
|
|
133
|
+
check("表恰 6 行", len(roles) == 6, str(len(roles)))
|
|
134
|
+
check("前 4 行 == 理论 4 行",
|
|
135
|
+
[r[0] for r in roles[:4]] == ["功能名", "生效条件", "子功能", "执行"],
|
|
136
|
+
str([r[0] for r in roles[:4]]))
|
|
137
|
+
check("后 2 行 == 新增两要素(验证方式 / 不适用条件)",
|
|
138
|
+
[r[0] for r in roles[4:]] == [nodefile.POSTCONDITION_FIELD,
|
|
139
|
+
nodefile.REJECTION_FIELD],
|
|
140
|
+
str([r[0] for r in roles[4:]]))
|
|
141
|
+
check("后 2 行的索引键名 == 条目里的键(单一真源)",
|
|
142
|
+
nodefile.INDEX_TERMS_KEYS == (nodefile.POSTCONDITION_TERMS_KEY,
|
|
143
|
+
nodefile.REJECTION_TERMS_KEY),
|
|
144
|
+
str(nodefile.INDEX_TERMS_KEYS))
|
|
145
|
+
check("6 行字段名集合与 CCG_MARKS 同源",
|
|
146
|
+
{r[0] for r in roles} == set(nodefile.CCG_MARKS),
|
|
147
|
+
str({r[0] for r in roles} ^ set(nodefile.CCG_MARKS)))
|
|
148
|
+
check("每行的索引键列非空", all(str(r[1]).strip() for r in roles))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def g2_single_parse_point():
|
|
152
|
+
print("== G2 取要素文本:走既有单点(非检索面自写正则)==")
|
|
153
|
+
colon = "# 功能名:x\n# 验证方式:compiler test 复现\n# 不适用条件:其它会话 无关查询\n"
|
|
154
|
+
nocolon = "# 功能名:x\n# 验证方式\ncompiler 复现\n# 不适用条件\n其它会话\n"
|
|
155
|
+
missing = "# 功能名:x\n# 执行:无\n"
|
|
156
|
+
check("冒号形态取值",
|
|
157
|
+
nodefile.ccg_element_terms(colon, "验证方式") == ["compiler", "test", "复现"],
|
|
158
|
+
str(nodefile.ccg_element_terms(colon, "验证方式")))
|
|
159
|
+
check("无冒号换行形态取值(行语义与 ccg_field_value 一致)",
|
|
160
|
+
nodefile.ccg_element_terms(nocolon, "验证方式") == ["compiler", "复现"],
|
|
161
|
+
str(nodefile.ccg_element_terms(nocolon, "验证方式")))
|
|
162
|
+
check("缺行 → 空表(不猜测)",
|
|
163
|
+
nodefile.ccg_element_terms(missing, "验证方式") == [])
|
|
164
|
+
# 与单点对拍:terms 必须 == element_terms_from_text(ccg_field_value(...))
|
|
165
|
+
ok = all(nodefile.ccg_element_terms(c, f)
|
|
166
|
+
== nodefile.element_terms_from_text(nodefile.ccg_field_value(c, f))
|
|
167
|
+
for c in (colon, nocolon, missing)
|
|
168
|
+
for f in ("验证方式", "不适用条件"))
|
|
169
|
+
check("与 ccg_field_value 逐例对拍一致(取值单点未旁路)", ok)
|
|
170
|
+
check("词长下限与 mdcg.NEG_MIN_TERM 同值(同口径)",
|
|
171
|
+
nodefile.ELEMENT_TERM_MIN == M.NEG_MIN_TERM,
|
|
172
|
+
f"{nodefile.ELEMENT_TERM_MIN} vs {M.NEG_MIN_TERM}")
|
|
173
|
+
check("空值语义哨兵不入键(不适用条件:无 → 空表)",
|
|
174
|
+
nodefile.ccg_element_terms(colon.replace("其它会话 无关查询", "无"),
|
|
175
|
+
"不适用条件") == [],
|
|
176
|
+
str(nodefile.ccg_element_terms(
|
|
177
|
+
colon.replace("其它会话 无关查询", "无"), "不适用条件")))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def g3_entry_keys():
|
|
181
|
+
print("== G3 两键进索引条目(写路径 / 重建路径同口径)==")
|
|
182
|
+
root, cg = _corpus()
|
|
183
|
+
try:
|
|
184
|
+
e = cg.index["nodes"]["k_decl"]
|
|
185
|
+
check("条目含后置条件词键",
|
|
186
|
+
nodefile.POSTCONDITION_TERMS_KEY in e and
|
|
187
|
+
e[nodefile.POSTCONDITION_TERMS_KEY] == ["compiler", "test", "复现"],
|
|
188
|
+
str(e.get(nodefile.POSTCONDITION_TERMS_KEY)))
|
|
189
|
+
check("条目含拒绝域词键",
|
|
190
|
+
e.get(nodefile.REJECTION_TERMS_KEY) == ["其它会话", "无关查询"],
|
|
191
|
+
str(e.get(nodefile.REJECTION_TERMS_KEY)))
|
|
192
|
+
check("不声明拒绝域的节点该键为空",
|
|
193
|
+
(cg.index["nodes"]["k_nodecl"].get(
|
|
194
|
+
nodefile.REJECTION_TERMS_KEY) or []) == [],
|
|
195
|
+
str(cg.index["nodes"]["k_nodecl"].get(
|
|
196
|
+
nodefile.REJECTION_TERMS_KEY)))
|
|
197
|
+
# 两键是**扁指标量**:与 time_window/observation_position 同为条目直读标量
|
|
198
|
+
check("两键为 list(扁指标量,非嵌套对象)",
|
|
199
|
+
isinstance(e[nodefile.POSTCONDITION_TERMS_KEY], list)
|
|
200
|
+
and isinstance(e[nodefile.REJECTION_TERMS_KEY], list))
|
|
201
|
+
# 写路径 vs 重建路径:重建后再取,两键逐位相同
|
|
202
|
+
before = {k: e.get(k) for k in nodefile.INDEX_TERMS_KEYS}
|
|
203
|
+
cg.rebuild_index()
|
|
204
|
+
after = {k: cg.index["nodes"]["k_decl"].get(k)
|
|
205
|
+
for k in nodefile.INDEX_TERMS_KEYS}
|
|
206
|
+
check("写路径 == 重建路径(两键逐位相同)", before == after,
|
|
207
|
+
f"{before} vs {after}")
|
|
208
|
+
finally:
|
|
209
|
+
cg.close()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def g4_rebuild_protocol():
|
|
213
|
+
print("== G4 重建协议四条硬约束(§5.3)==")
|
|
214
|
+
root, cg = _corpus()
|
|
215
|
+
try:
|
|
216
|
+
seq_a = [(r[0]["id"], round(float(r[1]), 6))
|
|
217
|
+
for r in cg.search_rrf("苹果 章节", k=5, record=False)[0]]
|
|
218
|
+
src_a = _tree_hash(root)
|
|
219
|
+
idx_path = cg.index_path
|
|
220
|
+
cg.rebuild_index() # 第一次:写快照(幂等对拍的左值)
|
|
221
|
+
fp_a = cg.index.get("_fingerprint")
|
|
222
|
+
with open(idx_path, "rb") as f:
|
|
223
|
+
bytes_a = f.read()
|
|
224
|
+
cg.rebuild_index() # 第二次:同源重建(对拍右值)
|
|
225
|
+
fp_b = cg.index.get("_fingerprint")
|
|
226
|
+
with open(idx_path, "rb") as f:
|
|
227
|
+
bytes_b = f.read()
|
|
228
|
+
src_b = _tree_hash(root)
|
|
229
|
+
seq_b = [(r[0]["id"], round(float(r[1]), 6))
|
|
230
|
+
for r in cg.search_rrf("苹果 章节", k=5, record=False)[0]]
|
|
231
|
+
check("① 幂等:同源重建 _fingerprint 对拍相等", fp_a == fp_b,
|
|
232
|
+
f"{fp_a} vs {fp_b}")
|
|
233
|
+
check("① 幂等:两次重建的 _index.json 字节相同", bytes_a == bytes_b)
|
|
234
|
+
check("③ 可回退:重建只覆盖派生物,源面逐字节不变",
|
|
235
|
+
src_a == src_b, f"{src_a} vs {src_b}")
|
|
236
|
+
check("④ 可对拍:重建前后合法次序逐位不变", seq_a == seq_b,
|
|
237
|
+
f"{seq_a} vs {seq_b}")
|
|
238
|
+
# ② 增量优先:指纹未变时 _maybe_reload_index 不换索引对象(不重扫)
|
|
239
|
+
obj_a = cg.index
|
|
240
|
+
cg._maybe_reload_index()
|
|
241
|
+
check("② 增量优先:指纹未变则不重建(索引对象不变)",
|
|
242
|
+
cg.index is obj_a)
|
|
243
|
+
finally:
|
|
244
|
+
cg.close()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def g5_dual_semantics():
|
|
248
|
+
print("== G5 拒绝域双语义:同一节点既边界命中、又未被资格 REJECT ==")
|
|
249
|
+
root, cg = _corpus()
|
|
250
|
+
try:
|
|
251
|
+
nd = cg.get("k_decl")
|
|
252
|
+
# ① 边界问句 + 查询自带拒绝域词面 → 边界命中 ∧ 非 REJECT
|
|
253
|
+
q1 = "什么条件下不适用 其它会话"
|
|
254
|
+
jb = M.MdCG.judge_with_boundary(nd, q1, None)
|
|
255
|
+
check("边界命中为真", jb["boundary"]["hit"] is True,
|
|
256
|
+
str(jb["boundary"]))
|
|
257
|
+
check("同一节点未被资格 REJECT(自否定 = 0)",
|
|
258
|
+
jb["qualification"]["state"] != M.STATE_REJECT,
|
|
259
|
+
str(jb["qualification"]))
|
|
260
|
+
check("可召回可展示", jb["recallable"] is True)
|
|
261
|
+
check("分开计数:boundary_hit 与 reject 是两个独立键",
|
|
262
|
+
jb["counts"]["boundary_hit"] == 1 and jb["counts"]["reject"] == 0,
|
|
263
|
+
str(jb["counts"]))
|
|
264
|
+
# ② 灵敏度不变:非边界问句 + 情境真的落在拒绝域 → 照旧 REJECT
|
|
265
|
+
jb2 = M.MdCG.judge_with_boundary(nd, "刚才在干什么", {"scene": "其它会话"})
|
|
266
|
+
check("非边界问句下情境落在拒绝域仍判 REJECT(灵敏度不变)",
|
|
267
|
+
jb2["qualification"]["state"] == M.STATE_REJECT,
|
|
268
|
+
str(jb2["qualification"]))
|
|
269
|
+
check("该情形同样分开计数(reject=1 / boundary_hit=1)",
|
|
270
|
+
jb2["counts"]["reject"] == 1 and jb2["counts"]["boundary_hit"] == 1,
|
|
271
|
+
str(jb2["counts"]))
|
|
272
|
+
# ③ 边界标记单点:两条来源(情境落在拒绝域 / 边界问句且已声明)
|
|
273
|
+
b1 = M.boundary_mark(nd, "什么条件下不适用", None)
|
|
274
|
+
b2 = M.boundary_mark(nd, "刚才在干什么", {"scene": "无关查询"})
|
|
275
|
+
check("边界问句且已声明拒绝域 → 命中",
|
|
276
|
+
b1["hit"] and b1["why"] == ["boundary_query_and_declared"],
|
|
277
|
+
str(b1))
|
|
278
|
+
check("情境落在拒绝域 → 命中(复用 P0-4 判据)",
|
|
279
|
+
b2["hit"] and "scene_in_rejection_domain" in b2["why"], str(b2))
|
|
280
|
+
check("marker 恒为 boundary_hit(展示标记,不是资格结论)",
|
|
281
|
+
b1["marker"] == M.BOUNDARY_MARKER)
|
|
282
|
+
finally:
|
|
283
|
+
cg.close()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def g6_default_recall():
|
|
287
|
+
print("== G6 六要素后两行全进默认检索(§5.4)==")
|
|
288
|
+
root, cg = _corpus()
|
|
289
|
+
try:
|
|
290
|
+
# 「何时不适用」:不撞 CCG 样板词面(生效条件/验证方式…),故若无索引键
|
|
291
|
+
# 则一个都召不回——正是「索引键驱动召回」的判别用例。
|
|
292
|
+
r, meta = cg.search_rrf("何时不适用", k=5, record=False)
|
|
293
|
+
ids = [x[0]["id"] for x in r]
|
|
294
|
+
check("边界问句召回「声明了拒绝域」的节点",
|
|
295
|
+
ids == ["k_decl"], str(ids))
|
|
296
|
+
check("该节点带 boundary_hit 标记",
|
|
297
|
+
bool(r) and r[0][0].get("boundary_hit") is True,
|
|
298
|
+
str([x[0].get("boundary_hit") for x in r]))
|
|
299
|
+
check("未声明拒绝域的节点不被召回", "k_nodecl" not in ids, str(ids))
|
|
300
|
+
check("meta 分开呈现边界读数(独立键)",
|
|
301
|
+
(meta.get("boundary") or {}).get("hit") == 1
|
|
302
|
+
and "boundary" in meta and "judge_filtered" in meta,
|
|
303
|
+
str(meta.get("boundary")))
|
|
304
|
+
check("默认路集仍为六路(不缺路)",
|
|
305
|
+
set((meta.get("paths") or {}).keys()) == {
|
|
306
|
+
"lexical", "bucket", "entity", "graph", "chain",
|
|
307
|
+
"temporal"}, str(sorted((meta.get("paths") or {}).keys())))
|
|
308
|
+
# 验证方式(第 5 行):按验证手段检索
|
|
309
|
+
r2, _ = cg.search_rrf("compiler", k=5, record=False)
|
|
310
|
+
check("按验证手段可召回(后置条件词索引键)",
|
|
311
|
+
"k_decl" in [x[0]["id"] for x in r2],
|
|
312
|
+
str([x[0]["id"] for x in r2]))
|
|
313
|
+
# 拒绝域**不作普通问句的召回键**(positive_body 的既有立场不变)
|
|
314
|
+
ent = cg.index["nodes"]["k_decl"]
|
|
315
|
+
terms_norm = M.expand_query_terms("其它会话")
|
|
316
|
+
check("普通问句:拒绝域键不参与召回",
|
|
317
|
+
M.index_key_hits(ent, terms_norm, "其它会话")["hit"] is False,
|
|
318
|
+
str(M.index_key_hits(ent, terms_norm, "其它会话")))
|
|
319
|
+
check("边界问句:拒绝域键参与召回",
|
|
320
|
+
M.index_key_hits(ent, terms_norm, "何时不适用")["reject"] is True,
|
|
321
|
+
str(M.index_key_hits(ent, terms_norm, "何时不适用")))
|
|
322
|
+
finally:
|
|
323
|
+
cg.close()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def g7_legacy_zero_break():
|
|
327
|
+
print("== G7 旧库零破坏(条目缺键 / 旧签名)==")
|
|
328
|
+
root, cg = _corpus()
|
|
329
|
+
try:
|
|
330
|
+
old = {"path": "knowledge/k_old.md", "tags": [], "importance": 0.5}
|
|
331
|
+
hits = M.index_key_hits(old, ["其它会话"], "何时不适用")
|
|
332
|
+
check("条目缺两键 → 判 False(未重建的旧快照退回旧行为)",
|
|
333
|
+
hits == {"hit": False, "post": False, "reject": False, "why": []},
|
|
334
|
+
str(hits))
|
|
335
|
+
# `_like` 三参旧签名(旧调用方)行为与改动前一致
|
|
336
|
+
body = BODY.format(n="x", neg="其它会话", t="苹果 章节")
|
|
337
|
+
check("_like 三参旧签名:按 positive_body 命中",
|
|
338
|
+
M.MdCG._like(body, {}, ["苹果"]) is True)
|
|
339
|
+
check("_like 三参旧签名:不适用条件行仍不作召回键",
|
|
340
|
+
M.MdCG._like(body, {}, ["其它会话"]) is False,
|
|
341
|
+
"positive_body 立场被破坏")
|
|
342
|
+
finally:
|
|
343
|
+
cg.close()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# --------------------------------------------------------------------------
|
|
347
|
+
# 定点变异(自证断言有判别力;只改本进程内存对象,绝不改仓库文件)
|
|
348
|
+
# --------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
def _mut_mark_min():
|
|
351
|
+
return _patch(nodefile, "ELEMENT_TERM_MIN", 1)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _mut_roles_4rows():
|
|
355
|
+
return _patch(nodefile, "CCG_INDEX_ROLES",
|
|
356
|
+
tuple(nodefile.CCG_INDEX_ROLES[:4]))
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _mut_boundary_markers():
|
|
360
|
+
return _patch(M, "BOUNDARY_QUERY_MARKERS", ())
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _mut_index_key_hits_off():
|
|
364
|
+
orig = M.index_key_hits
|
|
365
|
+
M.index_key_hits = lambda entry, terms, query: {
|
|
366
|
+
"hit": False, "post": False, "reject": False, "why": []}
|
|
367
|
+
|
|
368
|
+
def restore():
|
|
369
|
+
M.index_key_hits = orig
|
|
370
|
+
return restore
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _mut_boundary_no_strip():
|
|
374
|
+
orig = M.MdCG.judge_with_boundary
|
|
375
|
+
|
|
376
|
+
def shrunk(node_dict, query, context=None):
|
|
377
|
+
qual = dict(M.MdCG.judge_qualification(node_dict, query, context))
|
|
378
|
+
st = str(qual.get("state") or "")
|
|
379
|
+
counts = {k: int(st == v) for k, v in
|
|
380
|
+
(("accept", M.STATE_ACCEPT), ("reject", M.STATE_REJECT),
|
|
381
|
+
("defer", M.STATE_DEFER), ("blindspot", M.STATE_BLINDSPOT))}
|
|
382
|
+
counts["boundary_hit"] = 0
|
|
383
|
+
return {"qualification": qual, "boundary": {"hit": False, "terms": [],
|
|
384
|
+
"marker": None},
|
|
385
|
+
"counts": counts,
|
|
386
|
+
"recallable": st not in (M.STATE_REJECT, M.STATE_BLINDSPOT)}
|
|
387
|
+
|
|
388
|
+
M.MdCG.judge_with_boundary = staticmethod(shrunk)
|
|
389
|
+
|
|
390
|
+
def restore():
|
|
391
|
+
M.MdCG.judge_with_boundary = orig
|
|
392
|
+
return restore
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _patch(mod, name, value):
|
|
396
|
+
orig = getattr(mod, name)
|
|
397
|
+
|
|
398
|
+
def restore():
|
|
399
|
+
setattr(mod, name, orig)
|
|
400
|
+
setattr(mod, name, value)
|
|
401
|
+
return restore
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
_MUTATIONS = [
|
|
405
|
+
("roles_4rows", "把六要素表退回 4 行", _mut_roles_4rows),
|
|
406
|
+
("term_min_1", "索引词长下限改成 1(与 NEG_MIN_TERM 分叉)", _mut_mark_min),
|
|
407
|
+
("no_boundary_markers", "边界问句标记表清空", _mut_boundary_markers),
|
|
408
|
+
("index_keys_off", "索引键召回判据恒 False", _mut_index_key_hits_off),
|
|
409
|
+
("no_boundary_strip", "边界问句不再摘词面(自否定回归)",
|
|
410
|
+
_mut_boundary_no_strip),
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _collect():
|
|
415
|
+
"""跑全部断言组,返回失败清单(变异自证用同一批断言)。"""
|
|
416
|
+
global PASS, FAIL, FAILS
|
|
417
|
+
PASS, FAIL, FAILS = 0, 0, []
|
|
418
|
+
g1_table_six_rows()
|
|
419
|
+
g2_single_parse_point()
|
|
420
|
+
g3_entry_keys()
|
|
421
|
+
g4_rebuild_protocol()
|
|
422
|
+
g5_dual_semantics()
|
|
423
|
+
g6_default_recall()
|
|
424
|
+
g7_legacy_zero_break()
|
|
425
|
+
return list(FAILS)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def main(argv):
|
|
429
|
+
if "--mutate" in argv:
|
|
430
|
+
if "--list" in argv:
|
|
431
|
+
for name, desc, _fn in _MUTATIONS:
|
|
432
|
+
print(f" {name:24s} {desc}")
|
|
433
|
+
return 0
|
|
434
|
+
base = _collect()
|
|
435
|
+
if base:
|
|
436
|
+
print(f"\n!! 基线不绿({len(base)} 条失败)——变异自证无意义:{base}")
|
|
437
|
+
return 1
|
|
438
|
+
bad = []
|
|
439
|
+
for name, desc, fn in _MUTATIONS:
|
|
440
|
+
for anchor in ():
|
|
441
|
+
pass
|
|
442
|
+
try:
|
|
443
|
+
restore = fn()
|
|
444
|
+
except AttributeError as exc: # 锚点漂移:硬失败
|
|
445
|
+
print(f" ANCHOR-MISS {name}: {exc}")
|
|
446
|
+
return 2
|
|
447
|
+
got = _collect()
|
|
448
|
+
restore()
|
|
449
|
+
if got:
|
|
450
|
+
print(f" MUTATE-OK {name}({desc})→ 转红 {len(got)} 条:{got[:3]}")
|
|
451
|
+
else:
|
|
452
|
+
bad.append(name)
|
|
453
|
+
print(f" MUTATE-FAIL {name}({desc})→ 仍全绿(断言无判别力)")
|
|
454
|
+
print(f"\ntest_p2_six_elements --mutate: "
|
|
455
|
+
f"{len(_MUTATIONS) - len(bad)}/{len(_MUTATIONS)} 变异按预期转红")
|
|
456
|
+
return 0 if not bad else 1
|
|
457
|
+
fails = _collect()
|
|
458
|
+
print(f"\ntest_p2_six_elements: {PASS} 通过 / {FAIL} 失败")
|
|
459
|
+
return 0 if not fails else 1
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
if __name__ == "__main__":
|
|
463
|
+
sys.exit(main(sys.argv[1:]))
|