@furongjun1999/dsh-memory 0.6.0 → 0.7.0
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 +618 -572
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/README.md +1 -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/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/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//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/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//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/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 +52 -2
- 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 +100 -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 +57 -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//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/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//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/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_e2e_locomo_qa.py +11 -4
- 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 +48 -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/freshness.py +511 -0
- package/md_cg/fsutil.py +454 -8
- package/md_cg/generation.py +409 -0
- package/md_cg/hotcache.py +4 -1
- 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 +4261 -3818
- package/md_cg/mdcg.py +4695 -3547
- package/md_cg/mdcos.py +4672 -4159
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +713 -576
- 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/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/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 +1436 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_auto_defaults.py +424 -0
- 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_boundary_hit.py +410 -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_en_pipeline.py +22 -13
- package/md_cg/test_generation_guard.py +352 -0
- 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 +1387 -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 +519 -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_n230_dirty_replay.py +375 -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_p2_six_elements.py +463 -0
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -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_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 +522 -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_semantic_canonical.py +5 -3
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_sleep.py +611 -0
- package/md_cg/test_sleep_p1.py +784 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_time_core_lint.py +968 -0
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_unify_default_off.py +701 -0
- package/md_cg/test_unify_scope.py +182 -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/aeis_core/time_core.py +8 -0
- 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 +4 -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/utf8_boot.py +237 -0
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 分区健康度自检对单桶库说假话(M3)+ protect.mark 不存在节点裸 None(H9)
|
|
3
|
+
|
|
4
|
+
M3(`md_cg/routing.py::bucket_health`)——自检对「从未分区」的库报「分区退化」:
|
|
5
|
+
① 巨桶判据 `top/n > 0.30` 在 nb == 1 时恒真(单桶必然 100%)⇒ 一把从未分区
|
|
6
|
+
的库报成「巨桶 · 分区失效」。判据缺 `nb > 1` 守卫,而同函数碎片化判据早
|
|
7
|
+
就带着这个守卫(同形缺口)。修复:巨桶判据加 `nb > 1`。
|
|
8
|
+
② nb == 1 时巨桶与期望扫描(同样恒 1.0)互为同一事实的两种写法 ⇒ 合并为一条
|
|
9
|
+
如实读数「单桶 · 条件路由本就不可用(未分区,非巨桶退化)」。**ok 仍为
|
|
10
|
+
False**(单桶库的一次查询确实是全量扫描——判据未放宽,只把假诊断换成真读数);
|
|
11
|
+
`expected_scan` 的**数值**仍在返回 dict 里(下游读的是数)。
|
|
12
|
+
③ `counts == {}` 而库里有节点时返回 `{'ok': True, 'reason': 'empty'}` ⇒ 索引
|
|
13
|
+
分桶表整体缺失被读成「健康」。修复:新增 `total_nodes` 形参(缺省 None)区分
|
|
14
|
+
「真空库」与「有节点但零分桶」。**向后兼容**:缺省 None 时返回与加参数前
|
|
15
|
+
**逐位一致**(下面 3a/3b/3e 用整 dict 相等钉死),既有三个调用点
|
|
16
|
+
(test_p0:101 / bench_axis_domain:156 / bench6_arms:183 只传 counts)行为一字不变。
|
|
17
|
+
|
|
18
|
+
H9(`md_cg/protect.py::mark`)——节点不存在时裸返回 `None`:
|
|
19
|
+
④ `None` 与「成功」在调用方眼里都非 dict:`mcp_server._protect_call:1217` 把
|
|
20
|
+
`protect.mark(...)` 的结果直接回给 MCP 客户端 ⇒ 不存在的 node_id 序列化成
|
|
21
|
+
`null`,读成「打标成功」,而 `_write_node` 从未发生(静默假成功)。修复:
|
|
22
|
+
返回 `{"ok": False, "error": "node_not_found", "node_id": node_id}`
|
|
23
|
+
(形态对齐 `trust.set_state` 的同名分支)。成功路径的返回键**不变**
|
|
24
|
+
(node_id / protected / reason)。
|
|
25
|
+
|
|
26
|
+
守卫(四组 + 源断言 + floor;独立临时根 + 哑 aux 根,绝不触在役库/~/.mdcg):
|
|
27
|
+
⓿ 隔离自证;① 巨桶守卫(含真巨桶仍报、30% 边界不报);② 单桶如实读数;
|
|
28
|
+
③ 空桶 + total_nodes(含缺省逐位一致);④ mark 负路由(含与 trust.set_state
|
|
29
|
+
同形、MCP 面透出);⑤ 源断言 + live floor。
|
|
30
|
+
|
|
31
|
+
运行:python -X utf8 -m md_cg.test_m3_h9_bucket_health_protect_mark
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import atexit
|
|
36
|
+
import os
|
|
37
|
+
import shutil
|
|
38
|
+
import sys
|
|
39
|
+
import tempfile
|
|
40
|
+
|
|
41
|
+
# ── 隔离前置:在任何 md_cg 子模块 import 之前把根指到临时目录(import 期可能把
|
|
42
|
+
# aux_root() 冻成常量;且绝不触在役库 D:/... 与真实 ~/.mdcg)。 ──
|
|
43
|
+
_TMP = tempfile.mkdtemp(prefix="m3h9_")
|
|
44
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
45
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
|
|
46
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
47
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
48
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_STATE_ROOT",
|
|
49
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES"):
|
|
50
|
+
os.environ.pop(_k, None)
|
|
51
|
+
|
|
52
|
+
from . import protect, routing, trust # noqa: E402
|
|
53
|
+
from .mdcos import MdCGOS # noqa: E402
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
57
|
+
except Exception: # noqa: BLE001
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
PASS = FAIL = LIVE = 0
|
|
61
|
+
LIVE_FLOOR = 6
|
|
62
|
+
FAILS = []
|
|
63
|
+
|
|
64
|
+
ROOT = os.path.join(_TMP, "cg1")
|
|
65
|
+
BODY = ("# 功能名:分桶健康度探针\n"
|
|
66
|
+
"# 生效条件:M3/H9 守卫临时库\n"
|
|
67
|
+
"# 子功能:给沙箱库写入可打标节点\n"
|
|
68
|
+
"# 执行:直接调用 cg.add\n"
|
|
69
|
+
"# 验证方式:本守卫断言\n"
|
|
70
|
+
"# 不适用条件:无\n"
|
|
71
|
+
"守卫探针正文。\n")
|
|
72
|
+
|
|
73
|
+
_CG = []
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check(name, cond, detail="", live=False):
|
|
77
|
+
global PASS, FAIL, LIVE
|
|
78
|
+
if live:
|
|
79
|
+
LIVE += 1
|
|
80
|
+
if cond:
|
|
81
|
+
PASS += 1
|
|
82
|
+
print(" [PASS] %s" % name)
|
|
83
|
+
else:
|
|
84
|
+
FAIL += 1
|
|
85
|
+
FAILS.append(name)
|
|
86
|
+
print(" [FAIL] %s · %s" % (name, detail))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _close_all():
|
|
90
|
+
for cg in _CG:
|
|
91
|
+
try:
|
|
92
|
+
cg.close()
|
|
93
|
+
except Exception: # noqa: BLE001
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _cleanup(path) -> bool:
|
|
98
|
+
import gc
|
|
99
|
+
import time
|
|
100
|
+
for _ in range(6):
|
|
101
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
102
|
+
if not os.path.exists(path):
|
|
103
|
+
return True
|
|
104
|
+
gc.collect()
|
|
105
|
+
time.sleep(0.25)
|
|
106
|
+
return not os.path.exists(path)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
atexit.register(lambda: (_close_all(), _cleanup(_TMP)))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _cg():
|
|
113
|
+
cg = MdCGOS(ROOT)
|
|
114
|
+
_CG.append(cg)
|
|
115
|
+
return cg
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _src(rel):
|
|
119
|
+
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), rel),
|
|
120
|
+
encoding="utf-8") as f:
|
|
121
|
+
return f.read()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _window(text, start, end):
|
|
125
|
+
i = text.index(start)
|
|
126
|
+
j = text.index(end, i + len(start))
|
|
127
|
+
return text[i:j]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def legacy_empty():
|
|
131
|
+
"""加 total_nodes 参数之前的空桶返回(逐位对照基线)。"""
|
|
132
|
+
return {"ok": True, "reason": "empty", "buckets": 0}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main():
|
|
136
|
+
# ================= ⓿ 隔离自证 =================
|
|
137
|
+
print("【⓿】隔离自证(临时根,不触在役库)")
|
|
138
|
+
cg = _cg()
|
|
139
|
+
check("0a 库根落在本次临时目录内(不指在役库)",
|
|
140
|
+
os.path.abspath(cg.root).startswith(os.path.abspath(_TMP)),
|
|
141
|
+
cg.root, live=True)
|
|
142
|
+
check("0b MDCG_ROOT 亦在临时目录内(import 期不会冻到 ~/.mdcg)",
|
|
143
|
+
os.path.abspath(os.environ["MDCG_ROOT"]).startswith(
|
|
144
|
+
os.path.abspath(_TMP)),
|
|
145
|
+
os.environ["MDCG_ROOT"], live=True)
|
|
146
|
+
nid = cg.add("m3h9_alpha", BODY, layer="knowledge")
|
|
147
|
+
cg.add("m3h9_beta", BODY, layer="knowledge")
|
|
148
|
+
cg.add("m3h9_gamma", BODY, layer="knowledge")
|
|
149
|
+
cg.flush()
|
|
150
|
+
check("0c 沙箱库确有节点(防 fixture 空转)",
|
|
151
|
+
len(cg.index["nodes"]) == 3, len(cg.index["nodes"]), live=True)
|
|
152
|
+
|
|
153
|
+
# ================= ① 巨桶判据加 nb > 1 守卫 =================
|
|
154
|
+
print("\n【①】巨桶判据(M3①)")
|
|
155
|
+
one = routing.bucket_health({"cond_a": 100})
|
|
156
|
+
check("1a 单桶 {a:100} 不再报「巨桶」",
|
|
157
|
+
not any("巨桶" in p for p in one["problems"]), str(one["problems"]))
|
|
158
|
+
multi = routing.bucket_health({"cond_a": 80, "cond_b": 10, "cond_c": 10})
|
|
159
|
+
check("1b 多桶真巨桶 {a:80,b:10,c:10} 仍报「巨桶:最大桶占 80.0%」(未放宽)",
|
|
160
|
+
any("巨桶" in p and "80.0%" in p for p in multi["problems"])
|
|
161
|
+
and multi["ok"] is False, str(multi["problems"]))
|
|
162
|
+
edge = routing.bucket_health({"cond_a": 3, "cond_b": 3, "cond_c": 3,
|
|
163
|
+
"cond_d": 1})
|
|
164
|
+
check("1c 30% 边界(top/n == 0.30 且期望扫描 28%)两条判据均不触发",
|
|
165
|
+
edge["ok"] is True and edge["problems"] == []
|
|
166
|
+
and edge["max_bucket_share"] == 0.3, str(edge))
|
|
167
|
+
frag = routing.bucket_health({"k1": 1, "k2": 1, "k3": 1, "k4": 1, "k5": 1,
|
|
168
|
+
"cond_a": 3})
|
|
169
|
+
check("1d 碎片化判据(nb > 1 + 单例率 > 50%)仍报(同族判据未被误伤)",
|
|
170
|
+
any("碎片化" in p for p in frag["problems"]), str(frag["problems"]))
|
|
171
|
+
|
|
172
|
+
# ================= ② 单桶如实读数 =================
|
|
173
|
+
print("\n【②】单桶如实读数(M3②)")
|
|
174
|
+
check("2a 单桶 problems 点名「单桶」+「条件路由……不可用」",
|
|
175
|
+
len(one["problems"]) == 1 and "单桶" in one["problems"][0]
|
|
176
|
+
and "不可用" in one["problems"][0], str(one["problems"]))
|
|
177
|
+
check("2b 单桶 ok 仍为 False(判据未放宽:单桶查询确实是全量扫描)",
|
|
178
|
+
one["ok"] is False, str(one))
|
|
179
|
+
check("2c 单桶的 expected_scan / max_bucket_share 数值面上仍是 1.0(数不丢)",
|
|
180
|
+
one["expected_scan"] == 1.0 and one["max_bucket_share"] == 1.0
|
|
181
|
+
and one["nodes"] == 100 and one["buckets"] == 1, str(one))
|
|
182
|
+
one1 = routing.bucket_health({"cond_a": 1})
|
|
183
|
+
check("2d 单桶单节点 {a:1} 同口径(不因 n==1 走别的分支)",
|
|
184
|
+
len(one1["problems"]) == 1 and "单桶" in one1["problems"][0]
|
|
185
|
+
and "不可用" in one1["problems"][0], str(one1))
|
|
186
|
+
|
|
187
|
+
# ================= ③ 空分桶 + total_nodes =================
|
|
188
|
+
print("\n【③】空分桶与 total_nodes(M3③)")
|
|
189
|
+
check("3a 缺省 total_nodes 时空 counts 返回与改动前逐位一致",
|
|
190
|
+
routing.bucket_health({}) == legacy_empty()
|
|
191
|
+
and routing.bucket_health({}, total_nodes=None) == legacy_empty()
|
|
192
|
+
and routing.bucket_health({"cond_a": 0}) == legacy_empty(),
|
|
193
|
+
"%r / %r / %r" % (routing.bucket_health({}),
|
|
194
|
+
routing.bucket_health({}, total_nodes=None),
|
|
195
|
+
routing.bucket_health({"cond_a": 0})))
|
|
196
|
+
check("3b 缺省时键集亦未变(不因新参数给空桶路径加键)",
|
|
197
|
+
set(routing.bucket_health({})) == set(legacy_empty()),
|
|
198
|
+
str(sorted(routing.bucket_health({}))))
|
|
199
|
+
check("3c 真空库(total_nodes=0)仍报 ok=True·empty(不误伤空库)",
|
|
200
|
+
routing.bucket_health({}, total_nodes=0) == legacy_empty(),
|
|
201
|
+
str(routing.bucket_health({}, total_nodes=0)))
|
|
202
|
+
bad = routing.bucket_health({}, total_nodes=7)
|
|
203
|
+
check("3d 有节点但零分桶(total_nodes=7)不得再报 ok=True",
|
|
204
|
+
bad["ok"] is False and bad["reason"] == "empty_buckets"
|
|
205
|
+
and bad["buckets"] == 0 and bad["nodes"] == 7
|
|
206
|
+
and any("分区信息缺失" in p for p in bad["problems"]), str(bad))
|
|
207
|
+
check("3e 多桶返回键集与改动前一致(7 键,无新增键)",
|
|
208
|
+
set(multi) == {"ok", "buckets", "nodes", "max_bucket_share",
|
|
209
|
+
"singleton_ratio", "expected_scan", "problems"},
|
|
210
|
+
str(sorted(multi)))
|
|
211
|
+
live_buckets = dict(cg.index.get("buckets", {}))
|
|
212
|
+
cg.index["buckets"] = {} # 只改内存:模拟分桶表整体缺失
|
|
213
|
+
n_nodes = len(cg.index["nodes"])
|
|
214
|
+
lie = routing.bucket_health(cg.index.get("buckets", {}))
|
|
215
|
+
truth = routing.bucket_health(cg.index.get("buckets", {}),
|
|
216
|
+
total_nodes=n_nodes)
|
|
217
|
+
cg.index["buckets"] = live_buckets
|
|
218
|
+
check("3f 真库场景:分桶表清空后有节点仍判 ok=False(缺省则复现旧假话)",
|
|
219
|
+
truth["ok"] is False and lie["ok"] is True
|
|
220
|
+
and truth["nodes"] == n_nodes, "%r / %r" % (lie, truth), live=True)
|
|
221
|
+
check("3g 键集对照:有节点零分桶与真空库是两种读数(非「空」的同义)",
|
|
222
|
+
set(truth) != set(legacy_empty())
|
|
223
|
+
and truth["reason"] != legacy_empty()["reason"], str(truth))
|
|
224
|
+
|
|
225
|
+
# ================= ④ protect.mark 负路由 =================
|
|
226
|
+
print("\n【④】protect.mark 不存在节点(H9④)")
|
|
227
|
+
res = protect.mark(cg, nid, "M3H9 守卫·显式保护标记")
|
|
228
|
+
check("4a 成功路径既有返回键与值不变(node_id/protected/reason)",
|
|
229
|
+
isinstance(res, dict) and res.get("node_id") == nid
|
|
230
|
+
and res.get("protected") is True
|
|
231
|
+
and res.get("reason") == "M3H9 守卫·显式保护标记", repr(res))
|
|
232
|
+
pro, why = protect.is_protected(cg, nid)
|
|
233
|
+
disk = cg.get(nid)["frontmatter"]
|
|
234
|
+
check("4b 成功路径仍真的打上保护位(内存条目 + 盘上 frontmatter)",
|
|
235
|
+
pro is True and disk.get("protected") is True
|
|
236
|
+
and disk.get("protection_reason") == "M3H9 守卫·显式保护标记",
|
|
237
|
+
"%r / %r" % (pro, disk), live=True)
|
|
238
|
+
miss = protect.mark(cg, "m3h9_不存在", "理由")
|
|
239
|
+
check("4c 不存在节点返回结构化负路由(ok/error/node_id)",
|
|
240
|
+
isinstance(miss, dict) and miss.get("ok") is False
|
|
241
|
+
and miss.get("error") == "node_not_found"
|
|
242
|
+
and miss.get("node_id") == "m3h9_不存在", repr(miss))
|
|
243
|
+
check("4d 不再裸返回 None(None 与成功在调用方不可区分)",
|
|
244
|
+
miss is not None, repr(miss))
|
|
245
|
+
ts = trust.set_state(cg, "m3h9_不存在", "verified")
|
|
246
|
+
check("4e 形态与仓内既有负路由同形(trust.set_state 不存在分支)",
|
|
247
|
+
isinstance(miss, dict) and set(miss) == set(ts)
|
|
248
|
+
and ts.get("error") == "node_not_found", "%r / %r" % (miss, ts))
|
|
249
|
+
odd = protect.mark(cg, None, "理由")
|
|
250
|
+
check("4f 畸形 node_id(None)不裸穿透且同形",
|
|
251
|
+
isinstance(odd, dict) and odd.get("error") == "node_not_found"
|
|
252
|
+
and odd.get("node_id") is None, repr(odd))
|
|
253
|
+
try:
|
|
254
|
+
from .mcp_server import _protect_call
|
|
255
|
+
except Exception as exc: # noqa: BLE001
|
|
256
|
+
print(" [INFO] _protect_call 未取到(%s: %s),跳过 MCP 面断言"
|
|
257
|
+
% (type(exc).__name__, exc))
|
|
258
|
+
else:
|
|
259
|
+
mcp = _protect_call(cg, {"action": "mark", "node_id": "m3h9_不存在",
|
|
260
|
+
"reason": "理由"})
|
|
261
|
+
check("4g MCP 面 mark 分支对不存在节点透出负路由(不再是 null)",
|
|
262
|
+
isinstance(mcp, dict) and mcp.get("ok") is False
|
|
263
|
+
and mcp.get("error") == "node_not_found", repr(mcp), live=True)
|
|
264
|
+
|
|
265
|
+
# ================= ⑤ 源断言 + floor =================
|
|
266
|
+
print("\n【⑤】源断言(单点接线在位)")
|
|
267
|
+
s_routing = _src("routing.py")
|
|
268
|
+
bh = _window(s_routing, "def bucket_health", "\ndef ")
|
|
269
|
+
check("5a 巨桶判据带 nb > 1 守卫(与碎片化判据同形)",
|
|
270
|
+
"if nb > 1 and top / n > 0.30:" in bh, bh[:400])
|
|
271
|
+
check("5b 巨桶/期望扫描在 nb == 1 时合并为单桶如实读数",
|
|
272
|
+
"if nb == 1:" in bh and "单桶" in bh and "elif expected_scan > 0.30:" in bh,
|
|
273
|
+
bh[-600:])
|
|
274
|
+
check("5c 新参数 total_nodes 带缺省值(向后兼容面)",
|
|
275
|
+
"def bucket_health(counts: dict, total_nodes: int = None)" in s_routing,
|
|
276
|
+
bh[:120])
|
|
277
|
+
s_prot = _src("protect.py")
|
|
278
|
+
mk = _window(s_prot, "def mark", "def stats")
|
|
279
|
+
check("5d mark 不存在分支返回 node_not_found 负路由(不再是 None)",
|
|
280
|
+
'"ok": False, "error": "node_not_found", "node_id": node_id' in mk
|
|
281
|
+
and "return None" not in mk, mk[:600])
|
|
282
|
+
check("5e 成功路径返回键未被改动(仍 node_id/protected/reason)",
|
|
283
|
+
'return {"node_id": node_id, "protected": True, "reason": reason}' in mk,
|
|
284
|
+
mk[-300:])
|
|
285
|
+
print(" [INFO] live 断言数 = %d(floor=%d)" % (LIVE, LIVE_FLOOR))
|
|
286
|
+
check("5f live 断言数不低于 floor(防空转绿)", LIVE >= LIVE_FLOOR,
|
|
287
|
+
"live=%d" % LIVE)
|
|
288
|
+
|
|
289
|
+
print("\n" + "=" * 64)
|
|
290
|
+
print("PASS=%d FAIL=%d LIVE=%d" % (PASS, FAIL, LIVE))
|
|
291
|
+
if FAILS:
|
|
292
|
+
print("失败项:" + ";".join(FAILS))
|
|
293
|
+
return 0 if FAIL == 0 else 1
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
try:
|
|
298
|
+
rc = main()
|
|
299
|
+
finally:
|
|
300
|
+
_close_all()
|
|
301
|
+
sys.exit(rc)
|