@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,363 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""test_read_face_input_gates · 读面输入闸(M1 显式 k=0 / M2 node_id 类型 / M4 action 来源)
|
|
3
|
+
|
|
4
|
+
运行:python -m md_cg.test_read_face_input_gates
|
|
5
|
+
|
|
6
|
+
三个缺陷族(2026-09-30 修复,真源 md_cg/mcp_server.py):
|
|
7
|
+
|
|
8
|
+
M1 · `k` 的「显式 0 也是显式」
|
|
9
|
+
六处 `k=int(a.get("k") or 20)` 把显式 0(以及 -1)当「没传」,静默换成默认
|
|
10
|
+
20 —— 调用方以为「要 0 条」,拿到的是 20 条且返回形似正常。修法:单点判据
|
|
11
|
+
`_int_arg(a, "k", 20)`(`is not None` 口径,与 budget_tokens 先例一致),
|
|
12
|
+
六处全部**委托**该助手的单点实现。库层是「k<=0 意味什么」的唯一判据真源
|
|
13
|
+
(实测 cg.search/recall 对 0/-1 均返空且不抛),故本层只判透传 vs 回落。
|
|
14
|
+
|
|
15
|
+
**同日补强(k 族同族残余 8 处)**:self_check(5) / scrub.sample(k→n→
|
|
16
|
+
DEFAULT_SAMPLE) / scrub.associate(30) / scrub.sweep(k→n→DEFAULT_SAMPLE) /
|
|
17
|
+
scrub.history(100) / task.find(k→limit→5) / route(10) / mdcg_reflect(10)
|
|
18
|
+
原也是 `int(a.get("k") or N)`(含 k→n / k→limit 链式)。收口口径同上单点;
|
|
19
|
+
链式站点写成 `_int_arg(a, "k", _int_arg(a, "n", D))`——**逐级都不吞显式 0**。
|
|
20
|
+
其中 task.find(库层 `max(int(k or 5), 1)`)与 scrub.history(库层
|
|
21
|
+
`records[-int(limit):]`,0 即「取全部」)在库层另有 0 的语义、端到端不可观察,
|
|
22
|
+
故改断**本层透传给库层的原值**(打桩捕获,见 M1-D13/D14)。
|
|
23
|
+
|
|
24
|
+
M2 · read 的 node_id 类型闸
|
|
25
|
+
非 str 的 node_id:哈希可算者(int/float/bool)cg.get() 取不到 →
|
|
26
|
+
`_node_view(None)` → **裸 null**;不可哈希者(list/dict)TypeError 逃出。
|
|
27
|
+
修后**类型错**给结构化 error `node_id_not_str`(附 got_type;写面对应闸
|
|
28
|
+
见 mdcg.py:1776 的 `非法 node_id 类型` ValueError,读面取负路由形态)。
|
|
29
|
+
|
|
30
|
+
**「节点不存在」有意不动**:`md_cg/protocol.py` 的 read 形态表已登记
|
|
31
|
+
`node_missing.returns_null = True`(note:"不是错误对象——客户端须先判空再
|
|
32
|
+
解析"),反向证据 test_protocol A11/B8/B10 钉住它;改它须连带改协议真源与
|
|
33
|
+
那三条断言(不在本次指派文件内)。故两态的可区分性为:类型错 =
|
|
34
|
+
`{"ok": False, "error": "node_id_not_str", ...}`,不存在 = `null`。
|
|
35
|
+
|
|
36
|
+
M4 · action 来源恒定在场
|
|
37
|
+
旧行为只在「推导过」时透出 action_derived;显式传 action 时返回里没有
|
|
38
|
+
任何痕迹,调用方无法判断 action 是自己传的还是系统挑的。修后返回 dict 里
|
|
39
|
+
`action_source ∈ {explicit, sig, default, none}` **恒定在场**(`action_derived`
|
|
40
|
+
保持旧契约:只标记「推导发生过」,故显式态下**不出现**,test_action_derive
|
|
41
|
+
的 C5 断言依赖这一点)。`_cg_call.__doc__` 载默认动作表,本文件含
|
|
42
|
+
**表 ↔ 文档同源守卫**(防文档与 `_ACTION_DEFAULT` 漂移)。
|
|
43
|
+
"""
|
|
44
|
+
import os
|
|
45
|
+
import re
|
|
46
|
+
import sys
|
|
47
|
+
import tempfile
|
|
48
|
+
|
|
49
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
50
|
+
|
|
51
|
+
# Windows cmd 默认 GBK:中文打印即 UnicodeEncodeError —— 自带 UTF-8 兜底。
|
|
52
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
53
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
54
|
+
|
|
55
|
+
from md_cg import mcp_server as M
|
|
56
|
+
from md_cg.mdcos import MdCGSecure
|
|
57
|
+
from md_cg.security import DEFAULT_SENSITIVITY, Principal
|
|
58
|
+
|
|
59
|
+
_ok = 0
|
|
60
|
+
_fail = []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check(name, cond, detail=""):
|
|
64
|
+
global _ok
|
|
65
|
+
if cond:
|
|
66
|
+
_ok += 1
|
|
67
|
+
print("[PASS] " + name)
|
|
68
|
+
else:
|
|
69
|
+
_fail.append(name)
|
|
70
|
+
print("[FAIL] %s · %s" % (name, str(detail)[:240]))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _raises(fn):
|
|
74
|
+
try:
|
|
75
|
+
fn()
|
|
76
|
+
return None
|
|
77
|
+
except Exception as e: # noqa: BLE001
|
|
78
|
+
return e
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
CCG = ("# 功能名:红按钮移动\n# 生效条件:问红按钮\n# 子功能:左移\n"
|
|
82
|
+
"# 执行:红按钮控制角色左移\n# 验证方式:test\n# 不适用条件:问蓝按钮\n\n"
|
|
83
|
+
"红按钮控制角色左移\n")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ==================== M1-A. 单点判据(纯函数) ====================
|
|
87
|
+
IA = M._int_arg
|
|
88
|
+
|
|
89
|
+
check("M1-A1 显式 0 原样透传(旧写法回落 20)", IA({"k": 0}, "k", 20) == 0,
|
|
90
|
+
IA({"k": 0}, "k", 20))
|
|
91
|
+
check("M1-A2 显式负数原样透传", IA({"k": -3}, "k", 20) == -3, IA({"k": -3}, "k", 20))
|
|
92
|
+
check("M1-A3 缺键回落默认", IA({}, "k", 20) == 20, IA({}, "k", 20))
|
|
93
|
+
check("M1-A4 显式 None 视为未传,回落默认", IA({"k": None}, "k", 20) == 20,
|
|
94
|
+
IA({"k": None}, "k", 20))
|
|
95
|
+
check("M1-A5 普通值原样(不回归)", IA({"k": 5}, "k", 20) == 5, IA({"k": 5}, "k", 20))
|
|
96
|
+
check("M1-A6 字符串数字可 int 化(与旧写法同宽)", IA({"k": "7"}, "k", 20) == 7,
|
|
97
|
+
IA({"k": "7"}, "k", 20))
|
|
98
|
+
check("M1-A7 判据是 is not None 而非真值(False 也透传)",
|
|
99
|
+
IA({"k": False}, "k", 20) == 0, IA({"k": False}, "k", 20))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ==================== M1-B. 源码级:六处全部委托单点 ====================
|
|
103
|
+
_SRC = open(M.__file__, encoding="utf-8", errors="replace").read()
|
|
104
|
+
_k_old = re.findall(r'k=int\(a\.get\("k"\)\s*or\s+20\)', _SRC)
|
|
105
|
+
_k_new = re.findall(r'k=_int_arg\(a, "k", 20\)', _SRC)
|
|
106
|
+
check("M1-B1 旧写法 k=int(a.get('k') or 20) 已清零(六处全改)",
|
|
107
|
+
len(_k_old) == 0, "残留 %d 处" % len(_k_old))
|
|
108
|
+
check("M1-B2 六处全部委托 _int_arg 单点(一处实现,六处调用)",
|
|
109
|
+
len(_k_new) == 6, "实测 %d 处:%s" % (len(_k_new), _k_new))
|
|
110
|
+
check("M1-B3 单点实现唯一(_int_arg 只定义一次)",
|
|
111
|
+
len(re.findall(r"^def _int_arg\(", _SRC, re.M)) == 1,
|
|
112
|
+
len(re.findall(r"^def _int_arg\(", _SRC, re.M)))
|
|
113
|
+
# ---- k 族**全量**收口(2026-09-30 补强):除 read 面六处外的其余 8 处站点 ----
|
|
114
|
+
# 旧写法在链式上还有 `int(a.get("k") or a.get("n") or N)` / `... or a.get("limit") or N`
|
|
115
|
+
# 两种形态,故用「`int(a.get(` 后紧跟 k 键 + or」的全族正则,避免只清直连形态。
|
|
116
|
+
_k_family_old = re.findall(r'int\(a\.get\("k"\)\s*or', _SRC)
|
|
117
|
+
check("M1-B4 k 族旧写法全量清零(含 k→n / k→limit 链式)",
|
|
118
|
+
len(_k_family_old) == 0, "残留 %d 处" % len(_k_family_old))
|
|
119
|
+
_K_SITES = (
|
|
120
|
+
("metacognition.self_check", r'k=_int_arg\(a, "k", 5\)\)'),
|
|
121
|
+
("scrub.sample",
|
|
122
|
+
r'cg, _int_arg\(a, "k", _int_arg\(a, "n", scrub\.DEFAULT_SAMPLE\)\),'),
|
|
123
|
+
("scrub.associate", r'limit=_int_arg\(a, "k", 30\),'),
|
|
124
|
+
("scrub.sweep",
|
|
125
|
+
r'cg, n=_int_arg\(a, "k", _int_arg\(a, "n", scrub\.DEFAULT_SAMPLE\)\),'),
|
|
126
|
+
("scrub.history", r'return scrub\.history\(cg, limit=_int_arg\(a, "k", 100\)\)'),
|
|
127
|
+
("task.find", r'cg, name, k=_int_arg\(a, "k", _int_arg\(a, "limit", 5\)\)\)'),
|
|
128
|
+
("op.route", r'res, meta = cg\.search\(intent, k=_int_arg\(a, "k", 10\),'),
|
|
129
|
+
("mdcg_reflect",
|
|
130
|
+
r'res, _ = cg\.search\(a\.get\("query", ""\), k=_int_arg\(a, "k", 10\),'),
|
|
131
|
+
)
|
|
132
|
+
_site_off = [(n, len(re.findall(p, _SRC)))
|
|
133
|
+
for n, p in _K_SITES if len(re.findall(p, _SRC)) != 1]
|
|
134
|
+
check("M1-B5 k 族 8 处站点逐一委托 _int_arg 单点(各恰 1 处)",
|
|
135
|
+
not _site_off, _site_off)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ==================== M2-A / M4-B. 源码级:文档与常量同源 ====================
|
|
139
|
+
_doc = M._cg_call.__doc__ or ""
|
|
140
|
+
_doc_pairs = set(re.findall(r"([a-z_]+)→([a-z_]+)", _doc))
|
|
141
|
+
_table = dict(M._ACTION_DEFAULT)
|
|
142
|
+
check("M4-B1 默认动作表在 _cg_call 文档字符串中逐项在场(调用方不必猜)",
|
|
143
|
+
_doc_pairs == set(_table.items()),
|
|
144
|
+
"缺=%s 多=%s" % (sorted(set(_table.items()) - _doc_pairs),
|
|
145
|
+
sorted(_doc_pairs - set(_table.items()))))
|
|
146
|
+
check("M4-B2 文档表覆盖 _ACTION_DEFAULT 全部 %d 项" % len(_table),
|
|
147
|
+
len([p for p in _doc_pairs if p[0] in _table]) == len(_table),
|
|
148
|
+
len(_doc_pairs))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ==================== 端到端(真 cg,临时库) ====================
|
|
152
|
+
root = tempfile.mkdtemp(prefix="mdcg_rfig_")
|
|
153
|
+
try:
|
|
154
|
+
p = Principal(actor="tester", clearance=DEFAULT_SENSITIVITY, can_write=True,
|
|
155
|
+
can_admin=True, role="designer", session="sess_a",
|
|
156
|
+
harness="test-harness")
|
|
157
|
+
cg = MdCGSecure(root, principal=p)
|
|
158
|
+
cg.add("n1", CCG, layer="knowledge", verification_basis="test")
|
|
159
|
+
cg.add("n2", CCG.replace("红按钮", "红按钮旁"), layer="knowledge")
|
|
160
|
+
|
|
161
|
+
# ---- M1-C. 端到端:k=0 不再是「没传」 ----
|
|
162
|
+
out0 = M._cg_call(cg, {"op": "read", "query": "红按钮", "k": 0})
|
|
163
|
+
check("M1-C1 read+query k=0 返回空结果(修前 20 条)",
|
|
164
|
+
out0.get("results") == [], str(out0.get("results"))[:80])
|
|
165
|
+
outd = M._cg_call(cg, {"op": "read", "query": "红按钮"})
|
|
166
|
+
check("M1-C2 read+query 不传 k 仍走默认 20(不回归)",
|
|
167
|
+
len(outd.get("results") or []) > 0, len(outd.get("results") or []))
|
|
168
|
+
outb = M._cg_call(cg, {"op": "read", "query": "红按钮",
|
|
169
|
+
"budget_tokens": 100000, "k": 0})
|
|
170
|
+
check("M1-C3 read+budget_tokens 路 k=0 同样透传(pack 为空)",
|
|
171
|
+
outb.get("pack") == [], str(outb.get("pack"))[:80])
|
|
172
|
+
outs = M._dispatch(cg, "mdcg_recall", {"query": "红按钮", "budget_tokens": 100000,
|
|
173
|
+
"k": 0})
|
|
174
|
+
check("M1-C4 mdcg_recall k=0 pack 为空(修前 20 条)",
|
|
175
|
+
outs.get("pack") == [], str(outs.get("pack"))[:80])
|
|
176
|
+
outss = M._dispatch(cg, "mdcg_search", {"query": "红按钮", "k": 0})
|
|
177
|
+
check("M1-C5 mdcg_search k=0 结果为空(修前 20 条)",
|
|
178
|
+
outss.get("results") == [], str(outss.get("results"))[:80])
|
|
179
|
+
# 两处 pooling 委托点:measure 把 k 原样回显在返回里,故可直接读读数
|
|
180
|
+
pm0 = M._sustain_call(cg, {"op": "sustain", "action": "pool_measure",
|
|
181
|
+
"queries": ["红按钮"], "k": 0})
|
|
182
|
+
check("M1-C6 pool_measure k=0 读数如实回显 0(修前回显 20)",
|
|
183
|
+
pm0.get("k") == 0, str(pm0.get("k")))
|
|
184
|
+
pmd = M._sustain_call(cg, {"op": "sustain", "action": "pool_measure",
|
|
185
|
+
"queries": ["红按钮"]})
|
|
186
|
+
check("M1-C7 pool_measure 不传 k 仍回落 20(不回归)",
|
|
187
|
+
pmd.get("k") == 20, str(pmd.get("k")))
|
|
188
|
+
pc0 = M._sustain_call(cg, {"op": "sustain", "action": "pool_compare",
|
|
189
|
+
"queries": ["红按钮"], "k": 0})
|
|
190
|
+
check("M1-C8 pool_compare k=0 读数如实回显 0(修前回显 20)",
|
|
191
|
+
pc0.get("k") == 0, str(pc0.get("k")))
|
|
192
|
+
# 库层是「k<=0」判据真源:不抛、返空(修法不自行报错的依据)
|
|
193
|
+
check("M1-C9 库层 cg.search(k=0) 不抛且返空(本层不自造报错的依据)",
|
|
194
|
+
cg.search("红按钮", k=0)[0] == [], cg.search("红按钮", k=0)[0])
|
|
195
|
+
|
|
196
|
+
# ---- M1-D. 端到端:k 族**其余 8 处**站点(同日补强,不再只覆盖 read 面) ----
|
|
197
|
+
# 判据面与 M1-C 同:本层只判「显式 0 透传 vs 缺省回落」,**0 在库层的含义由库层
|
|
198
|
+
# 定**——故逐站点断「显式 0 与缺省可区分」,不假定 0 一律等于「0 条」。
|
|
199
|
+
rf0 = M._dispatch(cg, "mdcg_reflect", {"query": "红按钮", "k": 0})
|
|
200
|
+
check("M1-D1 mdcg_reflect k=0 → n_results=0(修前回落 10 条)",
|
|
201
|
+
rf0.get("n_results") == 0, str(rf0.get("n_results")))
|
|
202
|
+
rfd = M._dispatch(cg, "mdcg_reflect", {"query": "红按钮"})
|
|
203
|
+
check("M1-D2 mdcg_reflect 缺省 k → 有结果(回落未被改坏)",
|
|
204
|
+
(rfd.get("n_results") or 0) > 0, str(rfd.get("n_results")))
|
|
205
|
+
rt0 = M._cg_dispatch(cg, {"op": "route", "intent": "红按钮", "k": 0})
|
|
206
|
+
check("M1-D3 route k=0 → knowledge 空(修前回落 10 条)",
|
|
207
|
+
rt0.get("knowledge") == [], len(rt0.get("knowledge") or []))
|
|
208
|
+
rtd = M._cg_dispatch(cg, {"op": "route", "intent": "红按钮"})
|
|
209
|
+
check("M1-D4 route 缺省 k → 有结果(回落未被改坏)",
|
|
210
|
+
len(rtd.get("knowledge") or []) > 0, len(rtd.get("knowledge") or []))
|
|
211
|
+
sm0 = M._scrub_call(cg, {"op": "scrub", "action": "sample", "k": 0})
|
|
212
|
+
check("M1-D5 scrub.sample k=0 → n=0(修前回落 DEFAULT_SAMPLE)",
|
|
213
|
+
sm0.get("n") == 0, str(sm0.get("n")))
|
|
214
|
+
smd = M._scrub_call(cg, {"op": "scrub", "action": "sample"})
|
|
215
|
+
check("M1-D6 scrub.sample 缺省 → 有样本(回落未被改坏)",
|
|
216
|
+
(smd.get("n") or 0) > 0, str(smd.get("n")))
|
|
217
|
+
as0 = M._scrub_call(cg, {"op": "scrub", "action": "associate",
|
|
218
|
+
"node_id": "n1", "k": 0})
|
|
219
|
+
check("M1-D7 scrub.associate k=0 → 联想 0 条(limit 修前回落 30)",
|
|
220
|
+
as0.get("n") == 0, str(as0.get("n")))
|
|
221
|
+
asd = M._scrub_call(cg, {"op": "scrub", "action": "associate",
|
|
222
|
+
"node_id": "n1"})
|
|
223
|
+
check("M1-D8 scrub.associate 缺省 → 有联想(回落未被改坏)",
|
|
224
|
+
(asd.get("n") or 0) > 0, str(asd.get("n")))
|
|
225
|
+
sw0 = M._scrub_call(cg, {"op": "scrub", "action": "sweep", "k": 0})
|
|
226
|
+
check("M1-D9 scrub.sweep k=0 → 抽样 0 条(修前回落 DEFAULT_SAMPLE)",
|
|
227
|
+
(sw0.get("sample") or {}).get("n") == 0,
|
|
228
|
+
str((sw0.get("sample") or {}).get("n")))
|
|
229
|
+
swd = M._scrub_call(cg, {"op": "scrub", "action": "sweep"})
|
|
230
|
+
check("M1-D10 scrub.sweep 缺省 → 有抽样(回落未被改坏)",
|
|
231
|
+
((swd.get("sample") or {}).get("n") or 0) > 0,
|
|
232
|
+
str((swd.get("sample") or {}).get("n")))
|
|
233
|
+
# self_check:k 决定「吃几条相似历史」——上面两次 mdcg_reflect 已写入反思记录
|
|
234
|
+
sc0 = M._metacognition_call(cg, {"op": "metacognition", "action": "self_check",
|
|
235
|
+
"query": "红按钮", "k": 0})
|
|
236
|
+
check("M1-D11 self_check k=0 → prior_attempts=0(修前回落 5 条历史)",
|
|
237
|
+
sc0.get("prior_attempts") == 0, str(sc0.get("prior_attempts")))
|
|
238
|
+
scd = M._metacognition_call(cg, {"op": "metacognition", "action": "self_check",
|
|
239
|
+
"query": "红按钮"})
|
|
240
|
+
check("M1-D12 self_check 缺省 → 吃到历史(回落未被改坏)",
|
|
241
|
+
(scd.get("prior_attempts") or 0) > 0, str(scd.get("prior_attempts")))
|
|
242
|
+
# task.find 与 scrub.history 两处:**库层对 k 另有语义**(`max(int(k or 5), 1)`
|
|
243
|
+
# 下限 1;`records[-int(limit):]` 下 limit=0 即「取全部」),端到端不可观察 ——
|
|
244
|
+
# 故改断**本层透传的原值**(打桩捕获库层入参,finally 恢复,零残留)。
|
|
245
|
+
from md_cg import scrub as _s_mod, tasks as _t_mod
|
|
246
|
+
_seen = {}
|
|
247
|
+
_orig_fs, _orig_sh = _t_mod.find_similar, _s_mod.history
|
|
248
|
+
try:
|
|
249
|
+
_t_mod.find_similar = lambda cg_, name, k=5: (
|
|
250
|
+
_seen.setdefault("task_find", []).append(k) or {"ok": True})
|
|
251
|
+
_s_mod.history = lambda cg_, limit=100: (
|
|
252
|
+
_seen.setdefault("scrub_history", []).append(limit)
|
|
253
|
+
or {"n": 0, "records": []})
|
|
254
|
+
M._task_call(cg, {"op": "task", "action": "find", "name": "红", "k": 0})
|
|
255
|
+
M._task_call(cg, {"op": "task", "action": "find", "name": "红"})
|
|
256
|
+
M._scrub_call(cg, {"op": "scrub", "action": "history", "k": 0})
|
|
257
|
+
M._scrub_call(cg, {"op": "scrub", "action": "history"})
|
|
258
|
+
finally:
|
|
259
|
+
_t_mod.find_similar, _s_mod.history = _orig_fs, _orig_sh
|
|
260
|
+
check("M1-D13 task.find 显式 k=0 原样透传库层(缺省才回落 5)",
|
|
261
|
+
_seen.get("task_find") == [0, 5], _seen.get("task_find"))
|
|
262
|
+
check("M1-D14 scrub.history 显式 k=0 原样透传库层(缺省才回落 100)",
|
|
263
|
+
_seen.get("scrub_history") == [0, 100], _seen.get("scrub_history"))
|
|
264
|
+
|
|
265
|
+
# ---- M2-B. 端到端:node_id 两态可区分、无裸 null、无 TypeError ----
|
|
266
|
+
ok1 = M._cg_call(cg, {"op": "read", "node_id": "n1"})
|
|
267
|
+
check("M2-B1 正路径:str node_id 仍返回节点视图(不回归)",
|
|
268
|
+
isinstance(ok1, dict) and ok1.get("id") == "n1", str(ok1)[:90])
|
|
269
|
+
ok2 = M._cg_call(cg, {"op": "read", "node_id": "n1", "offset": 1})
|
|
270
|
+
check("M2-B2 正路径带 offset 仍工作", isinstance(ok2, dict) and "content" in ok2,
|
|
271
|
+
str(ok2)[:90])
|
|
272
|
+
# 修前此处是**裸 null**(None);修后「不存在」**仍是 null**——protocol.py 的
|
|
273
|
+
# read 形态表登记 `node_missing.returns_null=True`,反向证据 test_protocol
|
|
274
|
+
# A11/B8/B10 钉住它(改它须连带改协议真源,不在本次指派文件内)。故此处断言的
|
|
275
|
+
# 是「协议形态零回归」,两态可区分性由 M2-B7 单独钉。
|
|
276
|
+
miss = M._cg_call(cg, {"op": "read", "node_id": "no_such_node"})
|
|
277
|
+
check("M2-B3 不存在的 str id 仍返回协议登记的 null(A11/B8/B10 零回归)",
|
|
278
|
+
miss is None, repr(miss)[:120])
|
|
279
|
+
check("M2-B4 两态可区分:类型错是结构化 error 对象,不存在是 null",
|
|
280
|
+
isinstance(M._cg_call(cg, {"op": "read", "node_id": 1}), dict)
|
|
281
|
+
and miss is None, "%r / %r" % (M._cg_call(cg, {"op": "read",
|
|
282
|
+
"node_id": 1}), miss))
|
|
283
|
+
for _bad, _tn in ((123, "int"), (3.5, "float"), (True, "bool"), (["x"], "list"),
|
|
284
|
+
({"a": 1}, "dict"), (("x",), "tuple")):
|
|
285
|
+
_r = _raises(lambda b=_bad: M._cg_call(cg, {"op": "read", "node_id": b}))
|
|
286
|
+
check("M2-B5 非 str node_id=%r 不抛异常(修前 list/dict 抛 TypeError)"
|
|
287
|
+
% (_bad,), _r is None, _r)
|
|
288
|
+
# 修前此处 list/dict 会 TypeError:捕成断言失败而不是拖崩整个文件,
|
|
289
|
+
# 否则后续断言连评估机会都没有(红项数会失真)。
|
|
290
|
+
try:
|
|
291
|
+
_o = M._cg_call(cg, {"op": "read", "node_id": _bad})
|
|
292
|
+
except Exception as _e: # noqa: BLE001
|
|
293
|
+
_o = {"_raised": "%s: %s" % (type(_e).__name__, _e)}
|
|
294
|
+
check("M2-B6 非 str node_id=%s → node_id_not_str(修前裸 null)" % _tn,
|
|
295
|
+
isinstance(_o, dict) and _o.get("ok") is False
|
|
296
|
+
and _o.get("error") == "node_id_not_str"
|
|
297
|
+
and _o.get("got_type") == _tn and _o.get("node_id") == _bad,
|
|
298
|
+
str(_o)[:140])
|
|
299
|
+
# 取值钳(先例:修前非 str 会返回裸 null/抛 TypeError,直接 .get 拖崩文件)
|
|
300
|
+
def _err(args):
|
|
301
|
+
try:
|
|
302
|
+
_r = M._cg_call(cg, args)
|
|
303
|
+
except Exception as _e: # noqa: BLE001
|
|
304
|
+
return "RAISED:%s" % type(_e).__name__
|
|
305
|
+
return _r.get("error") if isinstance(_r, dict) else _r
|
|
306
|
+
|
|
307
|
+
check("M2-B7 两态可区分:类型错 = 结构化 error,不存在 = 协议登记的 null",
|
|
308
|
+
_err({"op": "read", "node_id": 1}) == "node_id_not_str"
|
|
309
|
+
and _err({"op": "read", "node_id": "zzz"}) is None,
|
|
310
|
+
"type=%r miss=%r" % (_err({"op": "read", "node_id": 1}),
|
|
311
|
+
_err({"op": "read", "node_id": "zzz"})))
|
|
312
|
+
check("M2-B8 显式空串 node_id 不再静默降级为检索(判据 is not None)",
|
|
313
|
+
M._cg_call(cg, {"op": "read", "node_id": ""}) is None,
|
|
314
|
+
repr(M._cg_call(cg, {"op": "read", "node_id": ""}))[:140])
|
|
315
|
+
check("M2-B8b 空串走「节点不存在」路而非 query 检索路(无 results/meta)",
|
|
316
|
+
not isinstance(M._cg_call(cg, {"op": "read", "node_id": ""}), dict),
|
|
317
|
+
repr(M._cg_call(cg, {"op": "read", "node_id": ""}))[:140])
|
|
318
|
+
_znone = M._cg_call(cg, {"op": "read", "node_id": None, "query": "红按钮"})
|
|
319
|
+
check("M2-B9 node_id=None 仍是「未传」,走检索(不回归)",
|
|
320
|
+
"results" in _znone and len(_znone["results"]) > 0, str(_znone)[:100])
|
|
321
|
+
|
|
322
|
+
# ---- M4-C. 端到端:action_source 恒定在场 ----
|
|
323
|
+
_o, pid = M._dispatch(cg, "mdcg_propose", {"node_id": "p_act",
|
|
324
|
+
"content": CCG}), None
|
|
325
|
+
pid = _o.get("pid")
|
|
326
|
+
ex1 = M._cg_call(cg, {"op": "review", "action": "list"})
|
|
327
|
+
check("M4-C1 显式 action → action_source='explicit'",
|
|
328
|
+
ex1.get("action_source") == "explicit", str(ex1.get("action_source")))
|
|
329
|
+
check("M4-C2 显式态下 action_derived 不出现(保持旧契约,C5 依赖)",
|
|
330
|
+
"action_derived" not in ex1, sorted(ex1.keys())[:12])
|
|
331
|
+
sig = M._cg_call(cg, {"op": "review", "pid": pid, "decision": "accept",
|
|
332
|
+
"reason": "读面闸自测"})
|
|
333
|
+
check("M4-C3 签名推导 → action_source='sig' + action_derived + hint_action",
|
|
334
|
+
sig.get("action_source") == "sig" and sig.get("action_derived") is True
|
|
335
|
+
and bool(sig.get("hint_action")), str(sig)[:140])
|
|
336
|
+
dfl = M._cg_call(cg, {"op": "review"})
|
|
337
|
+
check("M4-C4 无签名走默认 → action_source='default' 且点名默认 action",
|
|
338
|
+
dfl.get("action_source") == "default" and dfl.get("action") == "list"
|
|
339
|
+
and "list" in (dfl.get("hint_action") or ""), str(dfl)[:140])
|
|
340
|
+
dfl2 = M._cg_call(cg, {"op": "protect"})
|
|
341
|
+
check("M4-C5 protect 缺省 stats 同样透出 action_source='default'",
|
|
342
|
+
dfl2.get("action_source") == "default" and dfl2.get("action") == "stats",
|
|
343
|
+
str({k: dfl2.get(k) for k in ("action", "action_source")}))
|
|
344
|
+
ex2 = M._cg_call(cg, {"op": "protect", "action": "stats"})
|
|
345
|
+
check("M4-C6 protect 显式 stats → 'explicit'",
|
|
346
|
+
ex2.get("action_source") == "explicit", str(ex2.get("action_source")))
|
|
347
|
+
none_ = M._cg_call(cg, {"op": "read", "query": "红按钮"})
|
|
348
|
+
check("M4-C7 该 op 无默认(read)→ action_source='none'(不编造默认)",
|
|
349
|
+
none_.get("action_source") == "none", str(none_.get("action_source")))
|
|
350
|
+
for _op in ("review", "task", "recent", "goal", "protect", "scrub", "link",
|
|
351
|
+
"theory", "ccg", "ref", "whitebox", "read", "status"):
|
|
352
|
+
_r = M._cg_call(cg, {"op": _op})
|
|
353
|
+
check("M4-C8 「推导发生过」恒定在场:op=%s" % _op,
|
|
354
|
+
isinstance(_r, dict) and "action_source" in _r, str(_r)[:90])
|
|
355
|
+
finally:
|
|
356
|
+
import shutil
|
|
357
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
358
|
+
|
|
359
|
+
print("=" * 64)
|
|
360
|
+
print("read_face_input_gates:%d 通过 / %d 失败" % (_ok, len(_fail)))
|
|
361
|
+
if _fail:
|
|
362
|
+
print("失败项:" + "; ".join(_fail))
|
|
363
|
+
sys.exit(1 if _fail else 0)
|