@furongjun1999/dsh-memory 0.4.8 → 0.4.9
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 +54 -26
- package/codebuddy/CODEBUDDY.md +196 -195
- package/codebuddy/README.md +13 -1
- package/codebuddy/mcp.json +9 -0
- package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -0
- package/docs/README.md +1 -1
- package/docs/discipline/harnesses.yaml +18 -7
- package/docs/discipline/templates/full.md.tmpl +4 -3
- package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
- package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
- package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
- package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
- package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
- package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
- package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -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 +102 -0
- package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
- package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +503 -0
- package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
- package/docs/mdcg//344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/345/245/221/347/272/246_v0.1.md +82 -0
- package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -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 +47 -45
- package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/{206_v0.4.md → 206_v0.5.md} +92 -4
- 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 +172 -0
- package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
- package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
- package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +2 -2
- package/docs/theory//347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +3 -3
- package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +2 -2
- 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 +434 -433
- package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
- package/dsh/README.md +33 -0
- package/dsh/cordis.yml.example +13 -7
- package/dsh/hive-mcp-probe.mjs +94 -0
- package/dsh/hive-mcp.example.yml +62 -0
- package/dsh/update-lingshu.bat +11 -0
- package/dsh/update-lingshu.ps1 +337 -0
- package/lib/hooks.d.ts +3 -0
- package/lib/hooks.js +17 -23
- package/lib/index.d.ts +4 -2
- package/lib/index.js +29 -6
- package/lib/lib/datapath.d.ts +76 -1
- package/lib/lib/datapath.js +199 -13
- package/lib/lib/mdcg_client.d.ts +6 -3
- package/lib/lib/mdcg_client.js +6 -5
- package/lib/lib/mutual.js +4 -4
- package/lib/lib/token_store.js +4 -5
- package/md_cg/audit.py +17 -2
- package/md_cg/autonomy.py +86 -15
- package/md_cg/backfill.py +36 -1
- package/md_cg/backfill_bigdomain.py +34 -0
- package/md_cg/bench6_arms.py +28 -1
- package/md_cg/bench6_common.py +10 -1
- package/md_cg/bench6_competitors.py +6 -1
- package/md_cg/bench_axis_domain.py +9 -1
- package/md_cg/bench_blind_comp.py +7 -1
- package/md_cg/bench_en_atoms_public.py +9 -0
- package/md_cg/bench_governance.py +348 -0
- package/md_cg/bench_lme_zh.py +16 -1
- package/md_cg/bench_locomo.py +2 -1
- package/md_cg/bench_locomo_zh.py +16 -1
- package/md_cg/bench_locomo_zh_public.py +4 -1
- package/md_cg/bench_longmem.py +2 -1
- package/md_cg/bench_membench.py +27 -1
- package/md_cg/bench_p0.py +4 -1
- package/md_cg/bench_progressive.py +13 -1
- package/md_cg/bench_role_views.py +238 -0
- package/md_cg/bench_task_ab.py +8 -1
- package/md_cg/bench_task_ab_llm.py +13 -1
- package/md_cg/bench_unified_en.py +6 -1
- package/md_cg/bench_zh_mad.py +20 -1
- package/md_cg/blindspot_tickets.py +123 -0
- package/md_cg/branches.py +12 -1
- package/md_cg/build_postings.py +73 -0
- package/md_cg/ccgc.py +67 -2
- package/md_cg/census.py +5 -1
- package/md_cg/chain.py +24 -3
- package/md_cg/codeindex.py +134 -17
- package/md_cg/coldverify.py +265 -0
- package/md_cg/comment_gate.py +338 -0
- package/md_cg/cond_compose.py +190 -0
- package/md_cg/cond_facts.py +155 -0
- package/md_cg/cond_template.json +107 -0
- package/md_cg/condition_anchor.py +143 -0
- package/md_cg/conformance.py +69 -4
- package/md_cg/consistency.py +24 -1
- package/md_cg/consolidate.py +53 -2
- package/md_cg/corpus.py +4 -0
- package/md_cg/crosscheck.py +42 -2
- package/md_cg/crypto.py +35 -1
- package/md_cg/d_meta.py +310 -0
- package/md_cg/datapath.py +201 -26
- package/md_cg/docindex.py +122 -1
- package/md_cg/eval_common.py +29 -1
- package/md_cg/evidence.py +27 -1
- package/md_cg/evolution.py +21 -1
- package/md_cg/export.py +11 -1
- package/md_cg/forgetting.py +23 -1
- package/md_cg/fsutil.py +18 -1
- package/md_cg/hotcache.py +214 -0
- package/md_cg/hyperedge.py +251 -0
- package/md_cg/identity.py +18 -1
- package/md_cg/insight.py +17 -1
- package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
- package/md_cg/lexicon/build_standard_en.py +171 -168
- package/md_cg/lexicon/expand_en_zh.py +6 -0
- package/md_cg/lifecycle.py +12 -1
- package/md_cg/linkref.py +281 -0
- package/md_cg/links.py +29 -1
- package/md_cg/mcp_server.py +362 -43
- package/md_cg/md_whitebox.py +53 -1
- package/md_cg/mdcg.py +1003 -27
- package/md_cg/mdcos.py +558 -36
- package/md_cg/metacognition.py +37 -2
- package/md_cg/migrate.py +4 -0
- package/md_cg/migrate_aeis.py +221 -213
- package/md_cg/migrate_roleplay.py +8 -0
- package/md_cg/migrate_wisdom_graph.py +14 -1
- package/md_cg/mreview/__main__.py +3 -0
- package/md_cg/mreview/bundle.py +8 -0
- package/md_cg/mreview/candidates.py +9 -0
- package/md_cg/mreview/govern.py +21 -1
- package/md_cg/mreview/locate.py +34 -0
- package/md_cg/mreview/pipeline.py +29 -1
- package/md_cg/mreview/ruleset.py +16 -1
- package/md_cg/nodefile.py +233 -3
- package/md_cg/pooling.py +23 -1
- package/md_cg/postings.py +298 -0
- package/md_cg/predict.py +89 -9
- package/md_cg/progressive.py +3 -0
- package/md_cg/protect.py +14 -1
- package/md_cg/protocol.py +372 -0
- package/md_cg/provenance.py +262 -0
- package/md_cg/reach.py +453 -0
- package/md_cg/refindex.py +47 -2
- package/md_cg/refine.py +20 -1
- package/md_cg/roleviews.py +89 -0
- package/md_cg/routing.py +76 -0
- package/md_cg/scrub.py +63 -2
- package/md_cg/security.py +26 -1
- package/md_cg/self_state.py +64 -1
- package/md_cg/selfreport.py +151 -0
- package/md_cg/semantic/canonical.py +5 -0
- package/md_cg/semantic/en_normalizer.py +364 -355
- package/md_cg/semantic/zh_en_atoms.py +139 -136
- package/md_cg/signer.py +41 -1
- package/md_cg/sources.py +583 -547
- package/md_cg/statushdr.py +179 -0
- package/md_cg/stg.py +59 -18
- package/md_cg/subgraph.py +23 -0
- package/md_cg/sustain.py +56 -1
- package/md_cg/tasks.py +26 -2
- package/md_cg/test_autonomy.py +26 -0
- package/md_cg/test_bench_governance.py +102 -0
- package/md_cg/test_blindspot_tickets.py +166 -0
- package/md_cg/test_ccgc.py +10 -0
- package/md_cg/test_codeindex.py +338 -0
- package/md_cg/test_comment_gate.py +187 -0
- package/md_cg/test_cond_compose_anchors.py +76 -0
- package/md_cg/test_condition_anchor.py +82 -0
- package/md_cg/test_d_meta.py +412 -0
- package/md_cg/test_datapath_root.py +188 -0
- package/md_cg/test_gain_gate.py +47 -1
- package/md_cg/test_hot_cold.py +187 -0
- package/md_cg/test_hyperedge.py +245 -0
- package/md_cg/test_linkref.py +306 -0
- package/md_cg/test_md_access_parity.py +15 -3
- package/md_cg/test_mr_m1.py +108 -18
- package/md_cg/test_mr_m3.py +8 -1
- package/md_cg/test_p26_refindex.py +49 -20
- package/md_cg/test_p27_docindex.py +236 -2
- package/md_cg/test_p2_mcp.py +1 -1
- package/md_cg/test_p31_insight.py +24 -0
- package/md_cg/test_p44_md_whitebox.py +14 -1
- package/md_cg/test_protocol.py +243 -0
- package/md_cg/test_reach.py +378 -0
- package/md_cg/test_reach_keys.py +201 -0
- package/md_cg/test_reach_meta_exits.py +145 -0
- package/md_cg/test_read_clip.py +8 -4
- package/md_cg/test_retr_s1.py +340 -0
- package/md_cg/test_retr_s1b.py +209 -0
- package/md_cg/test_retr_s3.py +194 -0
- package/md_cg/test_retr_s4.py +163 -0
- package/md_cg/test_retr_s5.py +200 -0
- package/md_cg/test_retr_s6.py +157 -0
- package/md_cg/test_retr_s7.py +385 -0
- package/md_cg/test_retr_s8_time.py +316 -0
- package/md_cg/test_retr_s9_edges.py +286 -0
- package/md_cg/test_retr_s9_entity_ctx.py +175 -0
- package/md_cg/test_review_conformance.py +59 -2
- package/md_cg/test_role_views.py +354 -0
- package/md_cg/test_trust.py +361 -0
- package/md_cg/test_units_poll.py +71 -0
- package/md_cg/test_v14_fixes.py +397 -0
- package/md_cg/test_validity_filter.py +280 -0
- package/md_cg/test_wisdom_md_store.py +7 -3
- package/md_cg/test_writepipe.py +5 -1
- package/md_cg/theory.py +16 -1
- package/md_cg/tokens.py +40 -8
- package/md_cg/tool_face.py +13 -2
- package/md_cg/trust.py +943 -0
- package/md_cg/twophase.py +12 -1
- package/md_cg/units.py +129 -10
- package/md_cg/vision_evidence.py +24 -1
- package/md_cg/weights.py +24 -1
- package/md_cg/whitebox.py +32 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +21210 -365
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +5078 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
- package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
- package/md_cg/writelimit.py +18 -4
- package/md_cg/writepipe.py +178 -7
- package/package.json +2 -2
- package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
- package/skills/skills/designer-perspective/scripts/designer.py +17 -1
- package/skills/skills/designer-perspective/tests/selftest.py +3 -1
- package/src/hooks.ts +17 -21
- package/src/index.ts +33 -6
- package/src/lib/datapath.ts +211 -13
- package/src/lib/mdcg_client.ts +12 -8
- package/src/lib/mutual.ts +411 -411
- package/src/lib/token_store.ts +4 -5
- package/zcode/AGENTS.md +196 -195
- package/zcode/README.md +4 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
|
@@ -20,20 +20,31 @@ R2 改造的验收(对照 docs/mdcg/认知图_索引与工程规范化_计划_
|
|
|
20
20
|
⑦ 不静默:truncated / skipped_suffixes / skipped_dirs 显式上报;排除为**追加**
|
|
21
21
|
(只增不减,内置 .git/.venv/node_modules 不可被关闭);只读契约(源 mtime 不变);
|
|
22
22
|
幂等(重跑节点数不变);`index_doc` 进 ALL_OPS 且与工具 schema 一致。
|
|
23
|
+
⑧ fence 往返列六件套的形状与边界:BINDING_FIELDS / binding_of / binding_key
|
|
24
|
+
(**不含行位**)/ binding_slug / validate_binding / binding_drift / locate。
|
|
25
|
+
为什么必须钉死:列形状漂移会让对账器把坏列当好消息(漏报);键若悄悄带上
|
|
26
|
+
行位,真源上方插一行就会让整库失配——两者都是静默退化,只有断言看得见。
|
|
27
|
+
⑨ 逐字节回放回归:对**真实索引出的卡**断言 `render(条目) + "\n" == 卡片正文`。
|
|
28
|
+
⑧ 只证「列可定位」;只有逐字节相等才证明卡片是真源条目的**派生物**而非另一份
|
|
29
|
+
副本(渲染漂移或人工改写时列全然不变,对账器看不出来)。外部历史库另段抽样
|
|
30
|
+
(设 MDCG_ROOT 则跑,未设如实 SKIP 不虚报通过)。
|
|
23
31
|
|
|
24
32
|
运行:python -m md_cg.test_p27_docindex
|
|
33
|
+
设 MDCG_ROOT=<认知图库根> 追加跑【12b】外部历史库逐字节回放抽样
|
|
25
34
|
"""
|
|
26
35
|
from __future__ import annotations
|
|
27
36
|
|
|
28
37
|
import os
|
|
38
|
+
import random
|
|
29
39
|
import shutil
|
|
30
40
|
import sys
|
|
31
41
|
import tempfile
|
|
32
42
|
|
|
33
|
-
from . import codeindex, corpus, docindex, nodefile, routing, tokens
|
|
43
|
+
from . import codeindex, corpus, docindex, nodefile, refindex, routing, tokens
|
|
34
44
|
from . import mcp_server
|
|
35
|
-
from .mdcos import MdCGOS
|
|
45
|
+
from .mdcos import MdCGOS, MdCGSecure # 生产路径:forget 属 OS 层,基础层 MdCG 无删除原语
|
|
36
46
|
from .mcp_server import call_tool
|
|
47
|
+
from .security import Principal
|
|
37
48
|
|
|
38
49
|
PASS = FAIL = 0
|
|
39
50
|
FAILS = []
|
|
@@ -55,6 +66,79 @@ def check(name, cond, detail=""):
|
|
|
55
66
|
print(f" [FAIL] {name} · {detail}")
|
|
56
67
|
|
|
57
68
|
|
|
69
|
+
def _norm(p):
|
|
70
|
+
"""路径归一(大小写 + 分隔符):root 归属比对用,避免同一目录被判成两个。"""
|
|
71
|
+
return os.path.normcase(os.path.normpath(str(p or "")))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# 逐字节回放:把「往返列」从「形状可校验」推进到「内容可无损重建」。
|
|
75
|
+
# 生效条件:cg 已索引完成的库;索引条目 tags 含 "doc" 且 frontmatter 有非空 doc_ref。
|
|
76
|
+
# 判据:真源同键条目 `render(条目)` 补一个换行 == 卡片正文——`nodefile.dumps` 对不以
|
|
77
|
+
# 换行结尾的正文补 '\n',故卡片正文恒比 render 多 1 字节(是常态,不是缺陷;判等
|
|
78
|
+
# 一律按 `render + "\n"`,避免把口径差当回归)。
|
|
79
|
+
# 为什么非此不可:列形状对(validate_binding)只保证「可定位」;只有逐字节相等才
|
|
80
|
+
# 证明卡片是真源条目的**派生物**而非另一份副本。渲染漂移或人工改写卡片正文时,
|
|
81
|
+
# 列全都不变(路径/行位/hash 都是真源侧的值),对账器看不出来——本函数才看得见。
|
|
82
|
+
# 悬空(真源已移出)/ 未解析如实计数:那是数据面事实,不冒充渲染失败。
|
|
83
|
+
def _replay_cards(cg, roots=None, sample=0, seed=7):
|
|
84
|
+
nodes = (getattr(cg, "index", {}) or {}).get("nodes") or {}
|
|
85
|
+
picks = []
|
|
86
|
+
for nid in sorted(nodes):
|
|
87
|
+
if "doc" not in ((nodes.get(nid) or {}).get("tags") or []):
|
|
88
|
+
continue
|
|
89
|
+
kind, ref = refindex.ref_of(cg.get(nid))
|
|
90
|
+
if kind != "doc_ref" or not ref:
|
|
91
|
+
continue
|
|
92
|
+
if roots is not None and _norm(ref.get("root")) not in roots:
|
|
93
|
+
continue
|
|
94
|
+
picks.append((nid, ref))
|
|
95
|
+
if sample and len(picks) > sample:
|
|
96
|
+
picks = sorted(random.Random(seed).sample(picks, sample))
|
|
97
|
+
out = {"sampled": len(picks), "ok": 0, "dangling": 0, "gone": 0,
|
|
98
|
+
"unreadable": 0, "mismatch": []}
|
|
99
|
+
cache = {}
|
|
100
|
+
for nid, ref in picks:
|
|
101
|
+
fp = (str(ref.get("root") or ""), str(ref.get("path") or ""))
|
|
102
|
+
ckey = (_norm(fp[0]), fp[1])
|
|
103
|
+
if ckey not in cache:
|
|
104
|
+
# 把「源文件已移出」(数据面事实,非缺陷)与其它 IO/抽取异常(可能是真
|
|
105
|
+
# 问题:权限、编码、源被改成无标题文档)分开计数——混为一谈会把真实
|
|
106
|
+
# 故障伪装成历史遗留,静默吞掉回归。三态:True 读得 / False 不存在 /
|
|
107
|
+
# None 读不了。
|
|
108
|
+
try:
|
|
109
|
+
with open(os.path.join(fp[0], fp[1]), "r", encoding="utf-8",
|
|
110
|
+
errors="replace") as f:
|
|
111
|
+
cache[ckey] = (True, docindex.extract(f.read(), fp[1]))
|
|
112
|
+
except FileNotFoundError:
|
|
113
|
+
cache[ckey] = (False, None)
|
|
114
|
+
except (OSError, ValueError):
|
|
115
|
+
cache[ckey] = (None, None)
|
|
116
|
+
hit, items = cache[ckey]
|
|
117
|
+
if hit is not True:
|
|
118
|
+
out["gone" if hit is False else "unreadable"] += 1
|
|
119
|
+
continue
|
|
120
|
+
node = cg.get(nid) or {}
|
|
121
|
+
want = docindex.binding_key(docindex.binding_of(node))
|
|
122
|
+
# 键(path#heading_path)在同名标题重复时可能命中多条:先按行位锁定同名代,
|
|
123
|
+
# 行位也漂了才回落首条(此时本来就判「不符」,不掩盖结论)。
|
|
124
|
+
cands = [i for i in items if docindex.binding_key(i) == want]
|
|
125
|
+
if not cands:
|
|
126
|
+
out["dangling"] += 1
|
|
127
|
+
continue
|
|
128
|
+
item = next((i for i in cands if i.get("lineno") == ref.get("lineno")),
|
|
129
|
+
cands[0])
|
|
130
|
+
got = docindex.render(item) + "\n"
|
|
131
|
+
content = node.get("content") or ""
|
|
132
|
+
if got == content:
|
|
133
|
+
out["ok"] += 1
|
|
134
|
+
continue
|
|
135
|
+
k = next((i for i in range(min(len(got), len(content)))
|
|
136
|
+
if got[i] != content[i]), min(len(got), len(content)))
|
|
137
|
+
out["mismatch"].append(
|
|
138
|
+
f"{nid}@{k} 回放={got[k:k + 24]!r} 卡片={content[k:k + 24]!r}")
|
|
139
|
+
return out
|
|
140
|
+
|
|
141
|
+
|
|
58
142
|
LONG = "这是总览段落," + "用以验证章节切块在长正文下的表现," * 12 + "结束。"
|
|
59
143
|
|
|
60
144
|
GUIDE_LINES = [
|
|
@@ -514,6 +598,156 @@ def main():
|
|
|
514
598
|
check("清幽灵后悬空仍为零", not (ck4.get("dangling") or []),
|
|
515
599
|
str(ck4.get("dangling"))[:130])
|
|
516
600
|
|
|
601
|
+
# ============================================ ⑪ fence 往返列(binding)
|
|
602
|
+
# 缺口:往返列(取列 / 造键 / 校验 / 漂移 / 反查)是「条件卡 ↔ Markdown 真源」
|
|
603
|
+
# 的可校验面,此前零测试。列形状一旦漂移,对账器会把坏列当好消息(漏报);
|
|
604
|
+
# 键若悄悄带上行位,真源上方插一行就会让整库失配——故两者都要钉死。
|
|
605
|
+
print("\n【11】fence 往返列:形状 / 键不含行位 / 校验 / 漂移 / 反查")
|
|
606
|
+
li = docindex.extract(GUIDE, "guide.md")
|
|
607
|
+
s9b = next(i for i in li if i["heading"] == "9. 分阶段实施")
|
|
608
|
+
rendered = docindex.render(s9b)
|
|
609
|
+
gnode = cg.get(g_id) or {}
|
|
610
|
+
g_ref = (gnode.get("frontmatter") or {}).get("doc_ref") or {}
|
|
611
|
+
b0 = docindex.binding_of(gnode)
|
|
612
|
+
check("必需列齐全且无空值",
|
|
613
|
+
isinstance(b0, dict)
|
|
614
|
+
and all(b0.get(k) not in (None, "") for k in docindex.BINDING_FIELDS),
|
|
615
|
+
str(sorted(docindex.BINDING_FIELDS)))
|
|
616
|
+
check("列面 = 必需列 + 附加列(无第二份列口径)",
|
|
617
|
+
set(b0 or {}) == set(docindex.BINDING_FIELDS)
|
|
618
|
+
| set(docindex.BINDING_OPTIONAL),
|
|
619
|
+
str(sorted(set(b0 or {}) ^ (set(docindex.BINDING_FIELDS)
|
|
620
|
+
| set(docindex.BINDING_OPTIONAL)))))
|
|
621
|
+
check("必需列/附加列 ⊆ 写侧 doc_ref 列(读写同一列面)",
|
|
622
|
+
(set(docindex.BINDING_FIELDS) | set(docindex.BINDING_OPTIONAL))
|
|
623
|
+
<= set(g_ref),
|
|
624
|
+
str(sorted(set(docindex.BINDING_FIELDS) - set(g_ref))))
|
|
625
|
+
check("binding_of 与直接读 frontmatter 同源(同一投影)",
|
|
626
|
+
b0 == docindex.binding_of({"frontmatter": {"doc_ref": g_ref}}))
|
|
627
|
+
check("binding_of 对非索引节点返回 None(不猜、不补默认值)",
|
|
628
|
+
docindex.binding_of(None) is None
|
|
629
|
+
and docindex.binding_of([]) is None
|
|
630
|
+
and docindex.binding_of({}) is None
|
|
631
|
+
and docindex.binding_of({"frontmatter": {"tags": ["doc"]}}) is None)
|
|
632
|
+
check("validate_binding:本仓卡片列形状合法(ok + 零 issue)",
|
|
633
|
+
docindex.validate_binding(b0) == {"ok": True, "issues": []},
|
|
634
|
+
str(docindex.validate_binding(b0)))
|
|
635
|
+
check("validate_binding:非 dict 如实报错且不抛",
|
|
636
|
+
docindex.validate_binding(None)
|
|
637
|
+
== {"ok": False, "issues": ["绑定不是字典(该节点无 doc_ref)"]})
|
|
638
|
+
lo0 = b0["lineno"]
|
|
639
|
+
bad_detail = []
|
|
640
|
+
for _nm, _b, _want in (
|
|
641
|
+
("缺列", {k: v for k, v in b0.items() if k != "anchor"},
|
|
642
|
+
"缺列 anchor"),
|
|
643
|
+
("类型错 root", {**b0, "root": 123},
|
|
644
|
+
"root 应为字符串,实为 int"),
|
|
645
|
+
("类型错 lineno", {**b0, "lineno": "十"}, "行位不是整数"),
|
|
646
|
+
("行位越界", {**b0, "lineno": 0}, "lineno 越界(0 < 1)"),
|
|
647
|
+
("区间倒置", {**b0, "end": lo0 - 1},
|
|
648
|
+
f"end 早于 lineno({lo0}-{lo0 - 1})")):
|
|
649
|
+
_v = docindex.validate_binding(_b)
|
|
650
|
+
if _v["ok"] or _want not in _v["issues"]:
|
|
651
|
+
bad_detail.append(f"{_nm}→{_v['issues']}")
|
|
652
|
+
check("validate_binding 逐类坏列均被点出(缺列/类型错/越界/区间倒置)",
|
|
653
|
+
not bad_detail, "; ".join(bad_detail)[:170])
|
|
654
|
+
b_shift = docindex.binding_of({"frontmatter": {"doc_ref": {
|
|
655
|
+
**g_ref, "lineno": lo0 + 40, "end": g_ref["end"] + 40}}})
|
|
656
|
+
check("键不含行位:行位位移不改键(重排后仍是同一章节)",
|
|
657
|
+
docindex.binding_key(b0) == docindex.binding_key(b_shift)
|
|
658
|
+
== "guide.md#总览/9. 分阶段实施",
|
|
659
|
+
docindex.binding_key(b0))
|
|
660
|
+
check("漂移按固定列序报出变化项(lineno → end → hash)",
|
|
661
|
+
docindex.binding_drift(b0, b_shift) == ["lineno", "end"]
|
|
662
|
+
and docindex.binding_drift(b0, {**b0, "hash": "zz"}) == ["hash"]
|
|
663
|
+
and docindex.binding_drift(
|
|
664
|
+
b0, {**b0, "hash": "zz", "lineno": lo0 + 1})
|
|
665
|
+
== ["lineno", "hash"],
|
|
666
|
+
str(docindex.binding_drift(b0, b_shift)))
|
|
667
|
+
check("同列零漂移(无变化不报 / 空值不炸)",
|
|
668
|
+
docindex.binding_drift(b0, dict(b0)) == []
|
|
669
|
+
and docindex.binding_drift(None, None) == [])
|
|
670
|
+
check("binding_key 缺 heading_path 回落 path#anchor · 非 dict 返空串",
|
|
671
|
+
docindex.binding_key({"path": "a.md", "anchor": "s1"}) == "a.md#s1"
|
|
672
|
+
and docindex.binding_key({"path": "a.md"}) == "a.md#"
|
|
673
|
+
and docindex.binding_key(None) == "")
|
|
674
|
+
check("binding_slug 与 render 正文「本条目属于」逐字同源",
|
|
675
|
+
docindex.binding_slug(b0) == "guide.md#9-分阶段实施"
|
|
676
|
+
and f"本条目属于 {docindex.binding_slug(b0)}" in rendered,
|
|
677
|
+
docindex.binding_slug(b0))
|
|
678
|
+
check("反查取最内层(父节区间内的子节优先,不回落父节)",
|
|
679
|
+
docindex.locate(li, 12)["heading"] == "9. 分阶段实施"
|
|
680
|
+
and docindex.locate(li, 30)["heading"] == "代码示例",
|
|
681
|
+
str((docindex.locate(li, 12) or {}).get("heading")))
|
|
682
|
+
check("反查区间闭合(标题行与末行都算本节)",
|
|
683
|
+
docindex.locate(li, s9b["lineno"])["heading"] == "9. 分阶段实施"
|
|
684
|
+
and docindex.locate(li, s9b["end"])["heading"] == "9. 分阶段实施")
|
|
685
|
+
check("反查越界/非整数/空表 → None(fail-closed 不猜)",
|
|
686
|
+
docindex.locate(li, 10 ** 6) is None
|
|
687
|
+
and docindex.locate(li, 0) is None
|
|
688
|
+
and docindex.locate(li, "x") is None
|
|
689
|
+
and docindex.locate(li, None) is None
|
|
690
|
+
and docindex.locate([], 10) is None)
|
|
691
|
+
|
|
692
|
+
# ================================ ⑫ 逐字节回放回归(往返列无损)
|
|
693
|
+
# ⑪ 只证「列可校验」;本段把口径推到内容无损:卡片必须是真源条目的派生物
|
|
694
|
+
# (render 补一个换行 == 卡片正文)。root 限定为本次真正索引过的两个真源,
|
|
695
|
+
# 断言只落在本轮写入的卡上——历史残留不参与,避免把脏数据当回归。
|
|
696
|
+
print("\n【12】逐字节回放回归:render(条目) + 换行 == 卡片正文")
|
|
697
|
+
cg.flush()
|
|
698
|
+
rp = _replay_cards(cg, roots={_norm(fx), _norm(DOCS)})
|
|
699
|
+
print(f" 取样 {rp['sampled']} 卡:一致 {rp['ok']} · 键未命中 "
|
|
700
|
+
f"{rp['dangling']} · 源已移出 {rp['gone']} · 读不了 "
|
|
701
|
+
f"{rp['unreadable']}")
|
|
702
|
+
check("回放取样命中卡片(root 限定=本次索引的两个真源)",
|
|
703
|
+
rp["sampled"] > 0, str(rp["sampled"]))
|
|
704
|
+
check("可读卡 100% 逐字节回放一致(写侧 render 与读侧同源)",
|
|
705
|
+
not rp["mismatch"],
|
|
706
|
+
f"一致 {rp['ok']}/{rp['sampled']};" + ";".join(rp["mismatch"][:2]))
|
|
707
|
+
check("零键未命中 / 零源已移出 / 零读不了(源在库则必可重定位并读回)",
|
|
708
|
+
rp["dangling"] == 0 and rp["gone"] == 0
|
|
709
|
+
and rp["unreadable"] == 0,
|
|
710
|
+
f"键未命中={rp['dangling']} 源已移出={rp['gone']} "
|
|
711
|
+
f"读不了={rp['unreadable']}")
|
|
712
|
+
check("回放账目闭合(一致 + 键未命中 + 源已移出 + 读不了 = 取样数)",
|
|
713
|
+
(rp["ok"] + rp["dangling"] + rp["gone"] + rp["unreadable"])
|
|
714
|
+
== rp["sampled"],
|
|
715
|
+
f"{rp['ok']}+{rp['dangling']}+{rp['gone']}+{rp['unreadable']}"
|
|
716
|
+
f" vs {rp['sampled']}")
|
|
717
|
+
|
|
718
|
+
# 历史抽样:真实认知图库(仓外数据面,历史卡最多)。以 MDCG_ROOT 显式声明;
|
|
719
|
+
# 裸 clone / CI 无此数据面 → 如实报 SKIP(不虚报通过、也不 FAIL)。
|
|
720
|
+
ext_root = os.environ.get("MDCG_ROOT") or ""
|
|
721
|
+
if not (ext_root and os.path.isdir(ext_root)):
|
|
722
|
+
print("\n【12b】历史抽样:跳过(未设 MDCG_ROOT 或目录不存在)"
|
|
723
|
+
"—— 本仓语料回放见【12】")
|
|
724
|
+
else:
|
|
725
|
+
print(f"\n【12b】历史抽样:外部认知图库 {ext_root}(只读身份)")
|
|
726
|
+
ext = MdCGSecure(ext_root, principal=Principal(
|
|
727
|
+
actor="p27-replay", clearance="secret", can_write=False,
|
|
728
|
+
can_admin=False, role="designer", auth_mode="local-cli"))
|
|
729
|
+
try:
|
|
730
|
+
hp = _replay_cards(ext, sample=120)
|
|
731
|
+
finally:
|
|
732
|
+
ext.close()
|
|
733
|
+
print(f" 抽样 {hp['sampled']} 卡:一致 {hp['ok']} · 键未命中 "
|
|
734
|
+
f"{hp['dangling']} · 源已移出 {hp['gone']} · 读不了 "
|
|
735
|
+
f"{hp['unreadable']}")
|
|
736
|
+
check("【12b】历史库抽样命中卡片(数据面可达)",
|
|
737
|
+
hp["sampled"] > 0, str(hp["sampled"]))
|
|
738
|
+
check("【12b】可读历史卡零回放不符(未被渲染漂移/人工改写腐化)",
|
|
739
|
+
not hp["mismatch"],
|
|
740
|
+
f"一致 {hp['ok']}/{hp['sampled']};"
|
|
741
|
+
+ ";".join(hp["mismatch"][:2]))
|
|
742
|
+
check("【12b】未读回的全部是「源已移出」(非 IO/抽取故障)",
|
|
743
|
+
hp["unreadable"] == 0,
|
|
744
|
+
f"源已移出={hp['gone']} 读不了={hp['unreadable']}")
|
|
745
|
+
check("【12b】抽样账目闭合(无静默丢弃)",
|
|
746
|
+
(hp["ok"] + hp["dangling"] + hp["gone"] + hp["unreadable"])
|
|
747
|
+
== hp["sampled"],
|
|
748
|
+
f"{hp['ok']}+{hp['dangling']}+{hp['gone']}+{hp['unreadable']}"
|
|
749
|
+
f" vs {hp['sampled']}")
|
|
750
|
+
|
|
517
751
|
finally:
|
|
518
752
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
519
753
|
|
package/md_cg/test_p2_mcp.py
CHANGED
|
@@ -268,7 +268,7 @@ def main():
|
|
|
268
268
|
info.get("surface") == "kernel"
|
|
269
269
|
and set(info.get("audit_kinds", {})) == {
|
|
270
270
|
"code", "image_desc", "text", "permission",
|
|
271
|
-
"work_done", "work_wip", "ccg_marks"},
|
|
271
|
+
"work_done", "work_wip", "ccg_marks", "hyperedge"},
|
|
272
272
|
str(list(info.get("audit_kinds", {})))[:120])
|
|
273
273
|
check("cg info 自描述 CCG 契约角色(裁定 B)",
|
|
274
274
|
info.get("ccg_contract", {}).get("生效条件") == "前置条件 precondition"
|
|
@@ -201,6 +201,30 @@ def main():
|
|
|
201
201
|
mfm.get("induced_concept") == cid
|
|
202
202
|
and cid in {e.get("target") for e in (mfm.get("edges") or [])},
|
|
203
203
|
str(mfm.get("induced_concept")))
|
|
204
|
+
# ---- 2026-09-19 阶段一:巩固留痕字段一等公民 ----
|
|
205
|
+
check("成员侧记 consolidated_into(规范名·巩固进哪一条)",
|
|
206
|
+
mfm.get("consolidated_into") == cid
|
|
207
|
+
and mfm.get("induced_concept") == cid,
|
|
208
|
+
str(mfm.get("consolidated_into")))
|
|
209
|
+
check("成员侧记 consolidated_at(何时巩固)",
|
|
210
|
+
bool(mfm.get("consolidated_at")), str(mfm.get("consolidated_at")))
|
|
211
|
+
check("概念侧记 consolidated_at(概念形成即巩固时刻)",
|
|
212
|
+
bool(cfm.get("consolidated_at"))
|
|
213
|
+
and cfm.get("induced_at") == cfm.get("consolidated_at"),
|
|
214
|
+
f"{cfm.get('consolidated_at')} / {cfm.get('induced_at')}")
|
|
215
|
+
check("前身可定位:概念侧 induced_from 与成员侧列互查一致",
|
|
216
|
+
set(cfm.get("induced_from") or []) == {"ind_1", "ind_2", "ind_3"}
|
|
217
|
+
and all(((cg.get(m) or {}).get("frontmatter") or {}
|
|
218
|
+
).get("consolidated_into") == cid
|
|
219
|
+
for m in ("ind_1", "ind_2", "ind_3")))
|
|
220
|
+
# add() 是**全量重建 fm**——覆写后留痕必须仍在(回读节点继承,非索引快照)
|
|
221
|
+
cg.add("ind_1", IND_A, layer="contextual", importance=0.4,
|
|
222
|
+
verification_basis="other", actor="p31")
|
|
223
|
+
mfm2 = (cg.get("ind_1") or {}).get("frontmatter") or {}
|
|
224
|
+
check("覆写后成员侧巩固留痕继承(consolidated_into/at 不丢)",
|
|
225
|
+
mfm2.get("consolidated_into") == cid
|
|
226
|
+
and bool(mfm2.get("consolidated_at")),
|
|
227
|
+
str(mfm2.get("consolidated_into")))
|
|
204
228
|
check("induce 留痕 _maintain.jsonl",
|
|
205
229
|
any(r.get("action") == "induce"
|
|
206
230
|
for r in consolidate._read_maintain(ROOT)))
|
|
@@ -28,7 +28,7 @@ import tempfile
|
|
|
28
28
|
|
|
29
29
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
30
30
|
|
|
31
|
-
from .md_whitebox import build_db_from_md # noqa: E402
|
|
31
|
+
from .md_whitebox import build_db_from_md, corpus_gap # noqa: E402
|
|
32
32
|
|
|
33
33
|
SOURCE_DB = os.path.join(_HERE, "whitebox_kb", "wisdom", "wisdom-book-cloud.db")
|
|
34
34
|
|
|
@@ -129,6 +129,19 @@ def _report(rows):
|
|
|
129
129
|
def main():
|
|
130
130
|
ok = True
|
|
131
131
|
|
|
132
|
+
# 依赖自辩(2026-09-20 v14 缺陷 F):源库与 md 语料均为 gitignored 本地
|
|
133
|
+
# 数据面——缺失时本模块自己打 SKIP 返回 0。旧形态下缺库会以未捕获的
|
|
134
|
+
# `sqlite3.OperationalError: unable to open database file` traceback 示人,
|
|
135
|
+
# 与兄弟目标「自建库后断言失败」构成同一根因的第三副面孔。
|
|
136
|
+
if not os.path.exists(SOURCE_DB):
|
|
137
|
+
print("SKIP test_p44_md_whitebox:依赖源库 %s(.gitignore 忽略,"
|
|
138
|
+
"需本地生成)" % SOURCE_DB)
|
|
139
|
+
return 0
|
|
140
|
+
_gap = corpus_gap()
|
|
141
|
+
if _gap:
|
|
142
|
+
print("SKIP test_p44_md_whitebox:%s" % _gap)
|
|
143
|
+
return 0
|
|
144
|
+
|
|
132
145
|
# ---- 1) 结构等价 ----
|
|
133
146
|
# force:从 md 语料**重新**还原,保证 access_count 等可变字段与源库快照
|
|
134
147
|
# 同起点(复用旧库会带上此前问答累积的 access,造成假差异)。
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 记忆动词协议 v1 形状钉死(静态对账 + 进程内真调形状)
|
|
3
|
+
|
|
4
|
+
运行:python -m md_cg.test_protocol
|
|
5
|
+
|
|
6
|
+
分工(三层,各自独立可裁):
|
|
7
|
+
A 静态——协议声明 ↔ MCP 面实现:声明五动词 / live 有分支 / reserved 无分支 /
|
|
8
|
+
形状声明完备;`_ops_from_source` 的 AST 提取鲁棒性(单引号 · `op in` 元组 ·
|
|
9
|
+
`op.strip().lower()` 三写法 + 负例 `action ==` 不误取 + 语法错误不炸)。
|
|
10
|
+
B 动态——进程内真调 `_cg_call`,按协议声明的 required 子集校验**真实返回**:
|
|
11
|
+
route / read 三态(node · list · pack,含目标缺失返回 null)/ write 铁律三键 /
|
|
12
|
+
forget 两动作 + 失败形态 / op 推导兜底透出。
|
|
13
|
+
C 集成——`conformance.check()` 确实带上了 protocol.* 四条断言且全绿。
|
|
14
|
+
|
|
15
|
+
边界(如实声明,不假装覆盖):
|
|
16
|
+
· write 的 committed 形态在本环境不可达:未配置 MDCG_POLICY_FILE 时校验闸门恒
|
|
17
|
+
DEFER 入 review_queue(实测),故 B 段对 write 断言「铁律三键 + 未 committed
|
|
18
|
+
时去向可见(gate|moved_to)」,并按实际走的那一态校验对应形状;
|
|
19
|
+
committed 形态的字段依据是 writepipe 链尾执行器的源码事实。
|
|
20
|
+
· 本测试**不连真实库**:库根为 tempdir,断言面是形状(键集),不是数据正确性。
|
|
21
|
+
"""
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
|
|
27
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
28
|
+
|
|
29
|
+
from md_cg import conformance
|
|
30
|
+
from md_cg import mcp_server as M
|
|
31
|
+
from md_cg import protocol as P
|
|
32
|
+
from md_cg.mdcos import MdCGSecure
|
|
33
|
+
from md_cg.security import DEFAULT_SENSITIVITY, Principal
|
|
34
|
+
|
|
35
|
+
_ok = 0
|
|
36
|
+
_fail = []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def check(name, cond, detail=""):
|
|
40
|
+
global _ok
|
|
41
|
+
if cond:
|
|
42
|
+
_ok += 1
|
|
43
|
+
print("[PASS] " + name)
|
|
44
|
+
else:
|
|
45
|
+
_fail.append(name)
|
|
46
|
+
print("[FAIL] %s · %s" % (name, str(detail)[:240]))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _p(actor="proto"):
|
|
50
|
+
return Principal(actor=actor, clearance=DEFAULT_SENSITIVITY,
|
|
51
|
+
can_write=True, can_admin=True, role="designer")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _sub(shape, ret):
|
|
55
|
+
"""required 子集判定——协议形状断言的原语。"""
|
|
56
|
+
return set(shape["required"]) <= set(ret or {})
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _raises(exc, fn):
|
|
60
|
+
try:
|
|
61
|
+
fn()
|
|
62
|
+
return False
|
|
63
|
+
except exc:
|
|
64
|
+
return True
|
|
65
|
+
except Exception: # noqa: BLE001
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ==================== A. 静态:协议声明 ↔ MCP 面实现 ====================
|
|
70
|
+
print("-- A 静态对账 --")
|
|
71
|
+
_a = P.audit()
|
|
72
|
+
|
|
73
|
+
check("A1 audit ok(声明与实现一致)", _a["ok"] is True, _a["errors"])
|
|
74
|
+
check("A2 协议 v1 且 draft 期未冻结(冻结是单向门)",
|
|
75
|
+
_a["protocol_version"] == 1 and _a["frozen"] is False,
|
|
76
|
+
(_a["protocol_version"], _a["frozen"]))
|
|
77
|
+
check("A3 声明五动词且顺序固定",
|
|
78
|
+
tuple(_a["declared"]) == ("route", "read", "write", "supersede", "forget"),
|
|
79
|
+
_a["declared"])
|
|
80
|
+
check("A4 live 四动词有实现分支",
|
|
81
|
+
set(_a["live"]) == {"route", "read", "write", "forget"}, _a["live"])
|
|
82
|
+
check("A5 supersede 登记 reserved 且未泄漏实现(不冒充)",
|
|
83
|
+
_a["reserved"] == ["supersede"] and _a["reserved_leaked"] == [],
|
|
84
|
+
_a["reserved"])
|
|
85
|
+
check("A6 无 live 动词缺实现分支", _a["missing_impl"] == [], _a["missing_impl"])
|
|
86
|
+
check("A7 扩展能力面如实报告(属能力不属违例)",
|
|
87
|
+
bool(_a["extension_ops"]) and "write" not in _a["extension_ops"],
|
|
88
|
+
_a["extension_ops"])
|
|
89
|
+
check("A8 每动词至少一形态、每形态带 when", all(
|
|
90
|
+
P.VERB_SPECS[v]["shapes"]
|
|
91
|
+
and all(s.get("when") for s in P.VERB_SPECS[v]["shapes"].values())
|
|
92
|
+
for v in _a["declared"]), "")
|
|
93
|
+
check("A9 write 各形态 required 均含铁律三键",
|
|
94
|
+
all(set(P.WRITE_TRIO) <= set(s["required"])
|
|
95
|
+
for s in P.VERB_SPECS["write"]["shapes"].values()), "")
|
|
96
|
+
check("A10 reserved 动词给出 map_to 现状映射证据",
|
|
97
|
+
bool(P.VERB_SPECS["supersede"].get("map_to")), "")
|
|
98
|
+
check("A11 read 显式登记 null 形态(目标缺失 ≠ 错误)",
|
|
99
|
+
P.shape_of("read", "node_missing").get("returns_null") is True, "")
|
|
100
|
+
check("A12 非法动词 fail-closed(shape_of 抛 KeyError)",
|
|
101
|
+
_raises(KeyError, lambda: P.shape_of("nosuch")), "")
|
|
102
|
+
check("A13 summary 单行含 ok=True", "ok=True" in P.summary(), P.summary())
|
|
103
|
+
|
|
104
|
+
_src = '''
|
|
105
|
+
def _d(op, action):
|
|
106
|
+
if op == "double":
|
|
107
|
+
return 1
|
|
108
|
+
if op == 'single':
|
|
109
|
+
return 2
|
|
110
|
+
if op in ("in_a", "in_b"):
|
|
111
|
+
return 3
|
|
112
|
+
if op.strip().lower() == "stripped":
|
|
113
|
+
return 4
|
|
114
|
+
if action == "not_op":
|
|
115
|
+
return 5
|
|
116
|
+
return 0
|
|
117
|
+
'''
|
|
118
|
+
_ops = P._ops_from_source(_src)
|
|
119
|
+
check("A14 AST 提取四写法齐(双引号/单引号/in 元组/strip().lower)",
|
|
120
|
+
set(_ops) == {"double", "single", "in_a", "in_b", "stripped"}, _ops)
|
|
121
|
+
check("A15 负例:action == 分支不被误取为 op", "not_op" not in _ops, _ops)
|
|
122
|
+
check("A16 语法错误返回空列表且不抛异常",
|
|
123
|
+
P._ops_from_source("if (op ==") == [], "")
|
|
124
|
+
check("A17 dispatch_ops 与 audit 同源(同一次提取口径)",
|
|
125
|
+
P.dispatch_ops()["ops"] == _a["actual"], P.dispatch_ops()["ops"])
|
|
126
|
+
|
|
127
|
+
# ==================== B. 动态:进程内真调形状 ====================
|
|
128
|
+
print("-- B 进程内真调形状 --")
|
|
129
|
+
_root = tempfile.mkdtemp(prefix="mdcg_proto_")
|
|
130
|
+
_body = ("# 功能名:协议形状探针\n# 生效条件:协议形状测试\n# 子功能:无\n"
|
|
131
|
+
"# 执行:探针\n# 验证方式:test_protocol 断言\n"
|
|
132
|
+
"# 不适用条件:非形状测试场景\n\n正文 material_proto")
|
|
133
|
+
try:
|
|
134
|
+
cg = MdCGSecure(_root, principal=_p())
|
|
135
|
+
|
|
136
|
+
_r = M._cg_call(cg, {"op": "route", "intent": "记忆动词协议形状"})
|
|
137
|
+
check("B1 route 形态 required 齐(knowledge/meta)",
|
|
138
|
+
_sub(P.shape_of("route"), _r), _r)
|
|
139
|
+
|
|
140
|
+
_w = M._cg_call(cg, {"op": "write", "content_kind": "text",
|
|
141
|
+
"layer": "knowledge", "tags": ["proto"],
|
|
142
|
+
"content": _body})
|
|
143
|
+
check("B2 write 铁律三键齐(ok/id/committed——落盘与否不靠猜)",
|
|
144
|
+
set(P.WRITE_TRIO) <= set(_w or {}), _w)
|
|
145
|
+
check("B3 write 未 committed 时去向可见(gate 或 moved_to)",
|
|
146
|
+
bool((_w or {}).get("committed"))
|
|
147
|
+
or ("gate" in (_w or {}) or "moved_to" in (_w or {})), _w)
|
|
148
|
+
if _w.get("committed"):
|
|
149
|
+
check("B3b write committed 形态 required 齐",
|
|
150
|
+
_sub(P.shape_of("write", "committed"), _w), _w)
|
|
151
|
+
else:
|
|
152
|
+
check("B3c write 闸门短路形态 required 齐(moved_to/verdict/hint)",
|
|
153
|
+
_sub(P.shape_of("write", "gate_short_circuit"), _w), _w)
|
|
154
|
+
check("B4 verdict 字段集与协议声明一致(写闸门短路态)",
|
|
155
|
+
set(P.VERDICT_FIELDS) <= set(_w.get("verdict") or {}), _w.get("verdict"))
|
|
156
|
+
|
|
157
|
+
_nid = "mem_proto1"
|
|
158
|
+
cg.add(_nid, _body, content_kind="text", layer="knowledge", tags=["proto"])
|
|
159
|
+
cg.flush()
|
|
160
|
+
|
|
161
|
+
_rn = M._cg_call(cg, {"op": "read", "node_id": _nid})
|
|
162
|
+
check("B5 read(node) 形态 required 齐(含 verification_state)",
|
|
163
|
+
_sub(P.shape_of("read", "node"), _rn),
|
|
164
|
+
list(_rn.keys()) if isinstance(_rn, dict) else _rn)
|
|
165
|
+
|
|
166
|
+
_rl = M._cg_call(cg, {"op": "read", "query": "协议形状探针"})
|
|
167
|
+
check("B6 read(list) 形态 required 齐(meta/results)",
|
|
168
|
+
_sub(P.shape_of("read", "list"), _rl), _rl)
|
|
169
|
+
|
|
170
|
+
_rp = M._cg_call(cg, {"op": "read", "query": "协议形状探针",
|
|
171
|
+
"budget_tokens": 300})
|
|
172
|
+
check("B7 read(pack) 形态 required 齐(pack/tokens_used/budget/skipped/recent/meta)",
|
|
173
|
+
_sub(P.shape_of("read", "pack"), _rp),
|
|
174
|
+
list(_rp.keys()) if isinstance(_rp, dict) else _rp)
|
|
175
|
+
|
|
176
|
+
check("B8 read 目标缺失返回 null(协议登记形态,非错误对象)",
|
|
177
|
+
M._cg_call(cg, {"op": "read", "node_id": "mem_不存在"}) is None, "")
|
|
178
|
+
|
|
179
|
+
_f = M._cg_call(cg, {"op": "forget", "node_id": _nid, "reason": "协议测试"})
|
|
180
|
+
check("B9 forget 成功形态 required 齐(ok/id/tombstone)",
|
|
181
|
+
_sub(P.shape_of("forget", "forgotten"), _f), _f)
|
|
182
|
+
check("B10 forget 后 read(node_id) 立即返回 null(软删对读面生效)",
|
|
183
|
+
M._cg_call(cg, {"op": "read", "node_id": _nid}) is None, "")
|
|
184
|
+
|
|
185
|
+
_ff = M._cg_call(cg, {"op": "forget", "node_id": _nid, "action": "restore"})
|
|
186
|
+
check("B11 restore 返回属声明两形态之一(restored / failed)",
|
|
187
|
+
_sub(P.shape_of("forget", "restored"), _ff)
|
|
188
|
+
or _sub(P.shape_of("forget", "failed"), _ff), _ff)
|
|
189
|
+
|
|
190
|
+
_fm = M._cg_call(cg, {"op": "forget", "node_id": "mem_不存在"})
|
|
191
|
+
check("B12 forget 目标缺失失败形态 required 齐(ok/error)",
|
|
192
|
+
_sub(P.shape_of("forget", "failed"), _fm), _fm)
|
|
193
|
+
|
|
194
|
+
_d1 = M._cg_call(cg, {"content": "推导探针 derive_probe_content"})
|
|
195
|
+
check("B13 op 推导兜底:content → write 且透出 op_derived",
|
|
196
|
+
(_d1 or {}).get("op") == "write" and _d1.get("op_derived") is True, _d1)
|
|
197
|
+
check("B14 推导附加键不破坏 write 铁律三键",
|
|
198
|
+
set(P.WRITE_TRIO) <= set(_d1 or {}), _d1)
|
|
199
|
+
check("B15 推导附加键集与协议声明一致(op/op_derived/hint)",
|
|
200
|
+
set(P.DERIVE_EXTRA_KEYS) <= set(_d1 or {}), _d1)
|
|
201
|
+
|
|
202
|
+
_d2 = M._cg_call(cg, {"query": "协议形状探针"})
|
|
203
|
+
check("B16 op 推导兜底:query → read",
|
|
204
|
+
(_d2 or {}).get("op") == "read"
|
|
205
|
+
and _sub(P.shape_of("read", "list"), _d2), _d2)
|
|
206
|
+
finally:
|
|
207
|
+
shutil.rmtree(_root, ignore_errors=True)
|
|
208
|
+
|
|
209
|
+
# ==================== C. 集成:conformance 静态对账 ====================
|
|
210
|
+
print("-- C conformance 集成 --")
|
|
211
|
+
_croot = tempfile.mkdtemp(prefix="mdcg_proto_conf_")
|
|
212
|
+
try:
|
|
213
|
+
# conformance.check 需要 `_index.json` 存在,而 flush() 只写分片日志
|
|
214
|
+
# (`_index_log/`)——只有 compact_index()/rebuild_index() 才落 `_index.json`。
|
|
215
|
+
_ccg = MdCGSecure(_croot, principal=_p("proto_conf"))
|
|
216
|
+
_ccg.add("mem_conf1", _body, content_kind="text", layer="knowledge",
|
|
217
|
+
tags=["proto"])
|
|
218
|
+
_ccg.rebuild_index()
|
|
219
|
+
_rep = conformance.check(_croot)
|
|
220
|
+
check("C1 报告含 protocol 段", bool(_rep.get("protocol")),
|
|
221
|
+
list(_rep.keys())[:12])
|
|
222
|
+
_ids = {c["id"]: c for c in _rep["checks"]}
|
|
223
|
+
for _cid in ("protocol.op.extension_surface", "protocol.verb.implemented",
|
|
224
|
+
"protocol.verb.reserved_clean", "protocol.shape.declared"):
|
|
225
|
+
_c = _ids.get(_cid)
|
|
226
|
+
check("C2 已钉入 conformance 且 ok:%s" % _cid,
|
|
227
|
+
bool(_c) and _c["ok"] is True, _c)
|
|
228
|
+
check("C3 协议外 op 面为报告项 WARN(能力不是违例)",
|
|
229
|
+
_ids["protocol.op.extension_surface"]["level"] == "WARN",
|
|
230
|
+
_ids["protocol.op.extension_surface"]["level"])
|
|
231
|
+
check("C4 protocol.* 全绿(无 FAIL 残留)",
|
|
232
|
+
[c["id"] for c in _rep["checks"]
|
|
233
|
+
if c["id"].startswith("protocol.") and not c["ok"]] == [],
|
|
234
|
+
[c["id"] for c in _rep["checks"]
|
|
235
|
+
if c["id"].startswith("protocol.") and not c["ok"]])
|
|
236
|
+
finally:
|
|
237
|
+
shutil.rmtree(_croot, ignore_errors=True)
|
|
238
|
+
|
|
239
|
+
print("=" * 60)
|
|
240
|
+
print("protocol:%d 通过 / %d 失败" % (_ok, len(_fail)))
|
|
241
|
+
if _fail:
|
|
242
|
+
print("失败项:" + "; ".join(_fail))
|
|
243
|
+
sys.exit(1 if _fail else 0)
|