@furongjun1999/dsh-memory 0.4.7 → 0.4.8
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 +92 -37
- package/codebuddy/CODEBUDDY.md +195 -185
- package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -0
- package/docs/README.md +111 -0
- package/docs/discipline/harnesses.yaml +215 -152
- package/docs/discipline/templates/full.md.tmpl +60 -58
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
- package/docs/experiments/m4-role-probe/census.py +86 -0
- package/docs/experiments/m4-role-probe/probe.py +89 -0
- package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
- package/docs/experiments/mapped_confidence/post_check.py +75 -0
- package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
- package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
- package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
- package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +9 -9
- package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
- 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 +261 -0
- package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
- 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 +148 -0
- package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
- package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
- package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
- package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
- 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 +4 -4
- package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
- package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
- package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
- package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
- package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -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 +134 -0
- package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
- 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/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
- 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 +221 -0
- package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -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 +34 -6
- package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
- package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
- package/dsh/README.md +1 -1
- package/lib/hooks.d.ts +5 -0
- package/lib/hooks.js +10 -1
- package/lib/lib/prompt_safety.d.ts +48 -0
- package/lib/lib/prompt_safety.js +62 -0
- package/md_cg/audit.py +76 -2
- package/md_cg/bench_membench.py +10 -5
- package/md_cg/bench_progressive.py +48 -9
- package/md_cg/branches.py +275 -0
- package/md_cg/ccgc.py +884 -0
- package/md_cg/conformance.py +662 -0
- package/md_cg/consistency.py +1 -1
- package/md_cg/corpus.py +1 -1
- package/md_cg/eval_common.py +6 -5
- package/md_cg/forgetting.py +11 -3
- package/md_cg/fsutil.py +63 -1
- package/md_cg/identity.py +2 -2
- package/md_cg/insight.py +2 -2
- package/md_cg/lifecycle.py +262 -0
- package/md_cg/mcp_server.py +547 -140
- package/md_cg/mdcg.py +206 -18
- package/md_cg/mdcos.py +512 -50
- package/md_cg/metacognition.py +2 -2
- package/md_cg/mreview/__init__.py +25 -0
- package/md_cg/mreview/__main__.py +107 -0
- package/md_cg/mreview/bundle.py +170 -0
- package/md_cg/mreview/candidates.py +253 -0
- package/md_cg/mreview/govern.py +674 -0
- package/md_cg/mreview/locate.py +905 -0
- package/md_cg/mreview/pipeline.py +701 -0
- package/md_cg/mreview/rules/duplication.json +21 -0
- package/md_cg/mreview/rules/field_coverage.json +54 -0
- package/md_cg/mreview/rules/source_license.json +21 -0
- package/md_cg/mreview/rules/template_flow.json +21 -0
- package/md_cg/mreview/ruleset.py +238 -0
- package/md_cg/nodefile.py +38 -0
- package/md_cg/predict.py +24 -6
- package/md_cg/protect.py +4 -4
- package/md_cg/refindex.py +41 -2
- package/md_cg/scrub.py +5 -1
- package/md_cg/self_state.py +1 -1
- package/md_cg/semantic/en_normalizer.py +85 -25
- package/md_cg/sustain.py +18 -0
- package/md_cg/tasks.py +447 -0
- package/md_cg/test_action_derive.py +203 -0
- package/md_cg/test_audit_rotate.py +270 -0
- package/md_cg/test_branches.py +249 -0
- package/md_cg/test_ccg_perturb.py +1 -1
- package/md_cg/test_ccgc.py +423 -0
- package/md_cg/test_conformance.py +343 -0
- package/md_cg/test_en_pipeline.py +24 -0
- package/md_cg/test_health_scale.py +173 -0
- package/md_cg/test_identity_attribution.py +6 -0
- package/md_cg/test_index_durability.py +224 -0
- package/md_cg/test_lifecycle.py +309 -0
- package/md_cg/test_mr_m1.py +679 -0
- package/md_cg/test_mr_m2.py +587 -0
- package/md_cg/test_mr_m3.py +703 -0
- package/md_cg/test_mr_m4.py +485 -0
- package/md_cg/test_p1.py +1 -1
- package/md_cg/test_p11_consistency.py +1 -1
- package/md_cg/test_p12_metacognition.py +2 -2
- package/md_cg/test_p16_self_state.py +1 -1
- package/md_cg/test_p26_refindex.py +1 -1
- package/md_cg/test_p27_docindex.py +22 -2
- package/md_cg/test_p28_refcheck.py +1 -1
- package/md_cg/test_p29_session_ingest_export.py +1 -1
- package/md_cg/test_p2_mcp.py +7 -1
- package/md_cg/test_p38_contextualize.py +1 -1
- package/md_cg/test_p39_vision_evidence.py +1 -1
- package/md_cg/test_p40_refine_worklist.py +1 -1
- package/md_cg/test_p41_evolve_patrol.py +1 -1
- package/md_cg/test_p42_provenance.py +1 -1
- package/md_cg/test_p43_pooling.py +41 -13
- package/md_cg/test_read_clip.py +137 -0
- package/md_cg/test_review_conformance.py +310 -0
- package/md_cg/test_tasks.py +409 -0
- package/md_cg/test_tool_face.py +189 -0
- package/md_cg/test_twophase.py +286 -0
- package/md_cg/test_writepipe.py +210 -0
- package/md_cg/theory.py +1 -1
- package/md_cg/tokens.py +95 -5
- package/md_cg/tool_face.py +250 -0
- package/md_cg/twophase.py +221 -0
- package/md_cg/units.py +546 -0
- package/md_cg/vision_evidence.py +1 -1
- package/md_cg/weights.py +1 -1
- package/md_cg/whitebox.py +1 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +28 -0
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +46 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- 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
- package/md_cg/writelimit.py +19 -3
- package/md_cg/writepipe.py +372 -0
- package/package.json +1 -1
- package/src/hooks.ts +10 -1
- package/src/lib/prompt_safety.ts +62 -0
- package/zcode/AGENTS.md +195 -185
- package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
- /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
- /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
- /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
- /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
- /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
- /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
- /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
- /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
- /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
- /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
- /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
- /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
- /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
- /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""M4 落库治理自测(确定性部分):rule 驱动靶子 + 来源链 + 五纪律 + CLI。
|
|
3
|
+
|
|
4
|
+
真源:docs/记忆评审系统_立项设计与施工交接_20260915.md §3 第 4 级 + §6 验收 + §7 负面清单。
|
|
5
|
+
|
|
6
|
+
纪律(同 test_mr_m1/m2/m3):
|
|
7
|
+
· **自备数据源**——合成节点 + tempfile 合成库,零依赖真源库(外部 clone 全绿)。
|
|
8
|
+
· **零删除实锤**——全相位跑完节点集合只增不减(M4 只写 frontmatter 一个字段)。
|
|
9
|
+
· **不猜**——无来源记 `unfillable` + 原因分布;域外值**停止降级**;工作角色不被推断。
|
|
10
|
+
· **可回滚**——写入值即回滚锚点;当前值被人工改动 → `conflict` 不覆盖。
|
|
11
|
+
|
|
12
|
+
覆盖:A 靶子定位(rule 驱动,引擎不写死) B 取值推导(只搬运已声明证据)
|
|
13
|
+
C 预演(零写入) D 执行(写前重查 / 留痕 / 幂等 / 零删除)
|
|
14
|
+
E 回滚(还原 / 删键 / 冲突) F CLI(fail-closed)
|
|
15
|
+
G 对照口径(复用 conformance,不复刻判据)
|
|
16
|
+
运行:python -m md_cg.test_mr_m4
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import contextlib
|
|
21
|
+
import hashlib
|
|
22
|
+
import io
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import sys
|
|
27
|
+
import tempfile
|
|
28
|
+
|
|
29
|
+
from . import conformance as CF
|
|
30
|
+
from . import crypto
|
|
31
|
+
from . import nodefile as NF
|
|
32
|
+
from .mdcos import MdCGOS
|
|
33
|
+
from .mreview import govern as GV
|
|
34
|
+
|
|
35
|
+
PASS = FAIL = SKIP = 0
|
|
36
|
+
FAILS = []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ok(cond, label):
|
|
40
|
+
global PASS, FAIL
|
|
41
|
+
if cond:
|
|
42
|
+
PASS += 1
|
|
43
|
+
else:
|
|
44
|
+
FAIL += 1
|
|
45
|
+
FAILS.append(label)
|
|
46
|
+
print(" FAIL %s" % label)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def skip(label):
|
|
50
|
+
global SKIP
|
|
51
|
+
SKIP += 1
|
|
52
|
+
print(" SKIP %s" % label)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------- 合成库 ----------------------------
|
|
56
|
+
|
|
57
|
+
BODY = ("# 功能名:示例能力\n# 生效条件:载体/位置:本地仓;时间:全时窗(任意时刻成立);"
|
|
58
|
+
"方法:test;约束:无\n# 子功能:a/b\n# 执行:python -m md_cg.demo\n"
|
|
59
|
+
"# 验证方式:test\n# 不适用条件:无\n正文。")
|
|
60
|
+
LOCKED = crypto.ENC_PREFIX + "QUJDREVG" + crypto.ENC_SUFFIX
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def mkroot(root, nodes):
|
|
64
|
+
"""合成认知图根:`_index.json` + 节点盘文件(与 test_mr_m3 同范式)。"""
|
|
65
|
+
os.makedirs(root, exist_ok=True)
|
|
66
|
+
idx = {}
|
|
67
|
+
for nid, spec in nodes.items():
|
|
68
|
+
fm = dict(spec.get("fm") or {})
|
|
69
|
+
fm.setdefault("id", nid)
|
|
70
|
+
content = spec.get("content")
|
|
71
|
+
content = BODY if content is None else content
|
|
72
|
+
rel = spec.get("path") or ("%s/%s.md" % (spec.get("layer") or "knowledge", nid))
|
|
73
|
+
p = os.path.join(root, rel)
|
|
74
|
+
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
75
|
+
with open(p, "w", encoding="utf-8") as f:
|
|
76
|
+
f.write(NF.dumps(fm, content))
|
|
77
|
+
meta = dict(spec.get("meta") or {})
|
|
78
|
+
meta.update({"id": nid, "path": rel,
|
|
79
|
+
"layer": spec.get("layer") or "knowledge"})
|
|
80
|
+
if "role" in fm:
|
|
81
|
+
meta["role"] = fm["role"]
|
|
82
|
+
idx[nid] = meta
|
|
83
|
+
with open(os.path.join(root, CF.INDEX_FILE), "w", encoding="utf-8") as f:
|
|
84
|
+
json.dump({"nodes": idx}, f, ensure_ascii=False)
|
|
85
|
+
return root
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def snapshot(root):
|
|
89
|
+
out = {}
|
|
90
|
+
for dp, _dns, fns in os.walk(root):
|
|
91
|
+
for fn in fns:
|
|
92
|
+
p = os.path.join(dp, fn)
|
|
93
|
+
with open(p, "rb") as f:
|
|
94
|
+
out[os.path.relpath(p, root)] = hashlib.md5(f.read()).hexdigest()
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def disk_role(root, nid, layer="knowledge"):
|
|
99
|
+
p = os.path.join(root, layer, "%s.md" % nid)
|
|
100
|
+
with open(p, encoding="utf-8") as f:
|
|
101
|
+
fm, _c = NF.loads(f.read())
|
|
102
|
+
return fm
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def base_nodes():
|
|
106
|
+
"""六类靶面各一条:成文标签 / writer / 无声明 / 已有值 / 域外 / 密文 + 域外层。"""
|
|
107
|
+
return {
|
|
108
|
+
"k1": {"fm": {"tags": ["role:knowledge", "主题"]}},
|
|
109
|
+
"k2": {"fm": {"writer": "dsh-memory"}},
|
|
110
|
+
"k3": {"fm": {"tags": ["无声明"]}},
|
|
111
|
+
"k4": {"fm": {"role": "user", "tags": ["role:knowledge"]}},
|
|
112
|
+
"k5": {"fm": {"tags": ["role:工具"]}},
|
|
113
|
+
"k6": {"content": LOCKED},
|
|
114
|
+
"c1": {"fm": {"tags": ["role:knowledge"]}, "layer": "contextual"},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def rm(path):
|
|
119
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------- A 组:靶子定位(rule 驱动)----------------------------
|
|
123
|
+
|
|
124
|
+
def phase_a(tmp):
|
|
125
|
+
print("[A] 靶子定位(规则库是靶子唯一真源,引擎不写死)")
|
|
126
|
+
rule = GV.role_rule()
|
|
127
|
+
ok(rule["rule_id"] == "R-ROLE-MISSING" and rule["field"] == "role",
|
|
128
|
+
"A1 从真规则库取到靶规则(field=%s)" % rule["field"])
|
|
129
|
+
ok(rule["layers"] == ["knowledge"], "A2 作用域取自 matcher.layer(%s)" % rule["layers"])
|
|
130
|
+
ok(rule["field"] == "role",
|
|
131
|
+
"A3 字段名取自 mechanical[%s].field(不写死)" % GV.CHECK_FIELD_ABSENT)
|
|
132
|
+
|
|
133
|
+
# 规则库可替换:换成临时规则库(field=其他字段)→ 引擎随之改靶,零代码改动
|
|
134
|
+
d = os.path.join(tmp, "rules")
|
|
135
|
+
os.makedirs(d, exist_ok=True)
|
|
136
|
+
with open(os.path.join(d, "x.json"), "w", encoding="utf-8") as f:
|
|
137
|
+
json.dump({"rules": [{"id": "R-ROLE-MISSING", "title": "改靶", "severity": "INFO",
|
|
138
|
+
"matcher": {"layer": ["contextual"]},
|
|
139
|
+
"mechanical": [{"check": "field_absent", "field": "owner"}]}]}, f)
|
|
140
|
+
r2 = GV.role_rule(rules_dir=d)
|
|
141
|
+
ok(r2["field"] == "owner" and r2["layers"] == ["contextual"],
|
|
142
|
+
"A4 改数据即改靶(field=%s layers=%s),引擎零改动" % (r2["field"], r2["layers"]))
|
|
143
|
+
|
|
144
|
+
for bad, label in (({"rules": []}, "A5 规则库无该规则 → ValueError(靶子失效必须红灯)"),
|
|
145
|
+
({"rules": [{"id": "R-ROLE-MISSING", "severity": "INFO",
|
|
146
|
+
"mechanical": [{"check": "field_absent", "field": "role"}]}]},
|
|
147
|
+
"A6 规则缺 matcher.layer → ValueError"),
|
|
148
|
+
({"rules": [{"id": "R-ROLE-MISSING", "severity": "INFO",
|
|
149
|
+
"matcher": {"layer": ["knowledge"]}, "mechanical": []}]},
|
|
150
|
+
"A7 规则缺 field_absent.field → ValueError")):
|
|
151
|
+
try:
|
|
152
|
+
GV.role_rule(rules=bad)
|
|
153
|
+
ok(False, label + "(未抛错)")
|
|
154
|
+
except ValueError:
|
|
155
|
+
ok(True, label)
|
|
156
|
+
|
|
157
|
+
root = mkroot(os.path.join(tmp, "a"), base_nodes())
|
|
158
|
+
try:
|
|
159
|
+
GV.role_plan(root, layer="contextual")
|
|
160
|
+
ok(False, "A8 跨层收窄应抛错(绝不跨层套用)")
|
|
161
|
+
except ValueError:
|
|
162
|
+
ok(True, "A8 跨层收窄抛错(绝不跨层套用)")
|
|
163
|
+
rep = GV.role_plan(root, layer="knowledge")
|
|
164
|
+
ok(rep["rule_layers"] == ["knowledge"] and rep["skipped_out_of_scope"] == 1,
|
|
165
|
+
"A9 层内收窄不抛错,域外条数如实上报(%d)" % rep["skipped_out_of_scope"])
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ---------------------------- B 组:取值推导 ----------------------------
|
|
169
|
+
|
|
170
|
+
def phase_b(tmp):
|
|
171
|
+
print("[B] 取值推导(只搬运已声明证据,绝不编造)")
|
|
172
|
+
root = mkroot(os.path.join(tmp, "b"), base_nodes())
|
|
173
|
+
|
|
174
|
+
# 默认档(tags + map),map 需显式补表(WRITER_ROLE_MAP 故意为空)
|
|
175
|
+
p = GV.role_plan(root)
|
|
176
|
+
ok(GV.SOURCE_LAYER not in p["sources"],
|
|
177
|
+
"B1 默认档不含 layer_default(%s)——不得用常量填满指标" % (p["sources"],))
|
|
178
|
+
ok(p["by_source"] == {"tags": 1}, "B2 默认档只认成文标签(%s)" % p["by_source"])
|
|
179
|
+
ok(p["unfillable_by_reason"].get(GV.REASON_NO_SOURCE) == 1,
|
|
180
|
+
"B3 无任何声明 → no_source_declared(不猜)")
|
|
181
|
+
ok(p["unfillable_by_reason"].get(GV.REASON_WRITER_NO_MAP + ":dsh-memory") == 1,
|
|
182
|
+
"B4 writer 有值但映射表空 → writer_no_mapping:<writer>(待终裁补表)")
|
|
183
|
+
ok(p["unfillable_by_reason"].get(GV.REASON_TAG_OOD + ":工具") == 1,
|
|
184
|
+
"B5 域外标签值如实上报(%s)" % p["unfillable_by_reason"])
|
|
185
|
+
ok(p["skipped_present"] == 1, "B6 已有 role 值者不重复回填")
|
|
186
|
+
ok(p["skipped_locked"] == 1, "B7 密文节点跳过(绝不解密回写)")
|
|
187
|
+
|
|
188
|
+
# ① tags 成文声明优先且直取
|
|
189
|
+
p2 = GV.role_plan(root, ids=["k1"], sources=("tags", "map", "layer_default"))
|
|
190
|
+
it = p2["items"][0]
|
|
191
|
+
ok(it["role"] == "knowledge" and it["source"] == "tags",
|
|
192
|
+
"B8 tags 声明直取(%s / %s)" % (it["role"], it["source"]))
|
|
193
|
+
ok(it["basis"] == GV.BASIS_TAG, "B9 依据落在 basis(可审计,%s)" % it["basis"])
|
|
194
|
+
|
|
195
|
+
# ② map 档:显式补表才生效
|
|
196
|
+
p3 = GV.role_plan(root, role_map={"dsh-memory": "knowledge"})
|
|
197
|
+
ok(p3["by_source"] == {"tags": 1, "map": 1},
|
|
198
|
+
"B10 映射表补声明后 map 档生效(%s)" % p3["by_source"])
|
|
199
|
+
ok(p3["unfillable_by_reason"].get(GV.REASON_WRITER_NO_MAP + ":dsh-memory") is None,
|
|
200
|
+
"B11 补表后原 writer_no_mapping 消失(原因分布随声明变化)")
|
|
201
|
+
|
|
202
|
+
# ② OOD:映射表写域外值 → 停止降级
|
|
203
|
+
p4 = GV.role_plan(root, role_map={"dsh-memory": "不存在的角色"})
|
|
204
|
+
ok(p4["unfillable_by_reason"].get(GV.REASON_MAP_OOD + ":不存在的角色") == 1,
|
|
205
|
+
"B13 映射表域外值 → map_role_out_of_domain(%s)" % p4["unfillable_by_reason"])
|
|
206
|
+
|
|
207
|
+
# ② 工作角色:映射表写 tool-output → 拒绝(不许把知识节点摘出默认召回)
|
|
208
|
+
p5 = GV.role_plan(root, role_map={"dsh-memory": "tool-output"})
|
|
209
|
+
ok(p5["unfillable_by_reason"].get(GV.REASON_MAP_WORK + ":tool-output") == 1,
|
|
210
|
+
"B14 映射表推成工作角色 → 拒绝(%s)" % p5["unfillable_by_reason"])
|
|
211
|
+
ok(p5["targeted"] == 1 and p5["by_source"] == {"tags": 1},
|
|
212
|
+
"B15 工作角色被拒后**不**回退到其它档(拒绝即终态)")
|
|
213
|
+
|
|
214
|
+
# ③ layer_default:显式开启才可用
|
|
215
|
+
p6 = GV.role_plan(root, sources=("tags", "map", "layer_default"))
|
|
216
|
+
ok(p6["by_source"].get("layer_default") == 2,
|
|
217
|
+
"B16 layer_default 档显式开启后生效(%s)" % p6["by_source"])
|
|
218
|
+
ok(p6["targeted"] > p["targeted"], "B17 开启后靶面扩大(%d → %d)"
|
|
219
|
+
% (p["targeted"], p6["targeted"]))
|
|
220
|
+
|
|
221
|
+
# tags 域外值不因开启更多档而降级
|
|
222
|
+
p7 = GV.role_plan(root, ids=["k5"], sources=("tags", "map", "layer_default"))
|
|
223
|
+
ok(p7["targeted"] == 0 and p7["unfillable_by_reason"].get(GV.REASON_TAG_OOD + ":工具") == 1,
|
|
224
|
+
"B18 标签域外值在最强档下仍不降级(显式声明 ≠ 无声明)")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------- C 组:预演(零写入)----------------------------
|
|
228
|
+
|
|
229
|
+
def phase_c(tmp):
|
|
230
|
+
print("[C] 预演(零写入)")
|
|
231
|
+
root = mkroot(os.path.join(tmp, "c"), base_nodes())
|
|
232
|
+
GV.role_plan(root) # 预热(首建实例可能补索引)
|
|
233
|
+
before = snapshot(root)
|
|
234
|
+
GV.role_plan(root, sources=GV.ALL_SOURCES)
|
|
235
|
+
GV.role_stats(root)
|
|
236
|
+
GV.history(root)
|
|
237
|
+
ok(snapshot(root) == before, "C1 预演 / 统计 / 留痕读全程零写入(指纹不变)")
|
|
238
|
+
|
|
239
|
+
p = GV.role_plan(root, sources=GV.ALL_SOURCES)
|
|
240
|
+
ok(p["dry_run"] is True and p["action"] == GV.ACTION_ROLE, "C2 预演自报只读")
|
|
241
|
+
ok(p["nodes_scanned"] == 6 and p["skipped_out_of_scope"] == 1,
|
|
242
|
+
"C3 扫描面=层内 6 / 域外 1(%d/%d)" % (p["nodes_scanned"], p["skipped_out_of_scope"]))
|
|
243
|
+
ok(p["targeted"] == 3, "C4 最强档靶面 3 条(%d)" % p["targeted"])
|
|
244
|
+
|
|
245
|
+
p2 = GV.role_plan(root, sources=GV.ALL_SOURCES, limit=2)
|
|
246
|
+
ok(len(p2["items"]) == 2 and p2["targeted"] == 3,
|
|
247
|
+
"C5 limit 只截清单不动靶面(items=%d targeted=%d)" % (len(p2["items"]), p2["targeted"]))
|
|
248
|
+
|
|
249
|
+
p3 = GV.role_plan(root, ids=["k3"])
|
|
250
|
+
ok(p3["nodes_scanned"] == 1 and p3["skipped_out_of_scope"] == 0,
|
|
251
|
+
"C6 ids 收窄同时收窄扫描面与域外计数")
|
|
252
|
+
p4 = GV.role_plan(root, prefix="k1")
|
|
253
|
+
ok(p4["nodes_scanned"] == 1, "C7 prefix 收窄(%d)" % p4["nodes_scanned"])
|
|
254
|
+
p5 = GV.role_plan(root, sources=GV.ALL_SOURCES, sample=2)
|
|
255
|
+
ok(len(p5.get("sample") or []) == 2, "C8 sample 抽样(%s)" % p5.get("sample"))
|
|
256
|
+
ok(p5["planned_ids"] == [i["id"] for i in p5["items"]],
|
|
257
|
+
"C9 planned_ids 与清单同源(%s)" % p5["planned_ids"])
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------- D 组:执行 ----------------------------
|
|
261
|
+
|
|
262
|
+
def write_node(root, nid, fm, layer="knowledge", content=None):
|
|
263
|
+
p = os.path.join(root, layer, "%s.md" % nid)
|
|
264
|
+
with open(p, encoding="utf-8") as f:
|
|
265
|
+
_fm, c = NF.loads(f.read())
|
|
266
|
+
with open(p, "w", encoding="utf-8") as f:
|
|
267
|
+
f.write(NF.dumps(fm, c if content is None else content))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def idx_role(root, nid):
|
|
271
|
+
with open(os.path.join(root, CF.INDEX_FILE), encoding="utf-8") as f:
|
|
272
|
+
return (json.load(f)["nodes"].get(nid) or {}).get("role")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def phase_d(tmp):
|
|
276
|
+
print("[D] 执行(写前重查 / 留痕 / 幂等 / 零删除)")
|
|
277
|
+
root = mkroot(os.path.join(tmp, "d"), base_nodes())
|
|
278
|
+
files0 = set(snapshot(root))
|
|
279
|
+
c1_0 = snapshot(root).get("contextual/c1.md")
|
|
280
|
+
|
|
281
|
+
rep = GV.role_apply(root, role_map={"dsh-memory": "knowledge"},
|
|
282
|
+
batch="b1", actor="tester")
|
|
283
|
+
ok(rep["written"] == 2 and rep["by_source"] == {"tags": 1, "map": 1},
|
|
284
|
+
"D1 写入 2 条(%s)" % rep["by_source"])
|
|
285
|
+
ok(disk_role(root, "k1")["role"] == "knowledge", "D2 tags 档写盘生效")
|
|
286
|
+
ok(disk_role(root, "k2")["role"] == "knowledge", "D3 map 档写盘生效")
|
|
287
|
+
ok("role" not in disk_role(root, "k3"), "D4 无来源者不写(宁可缺失不猜)")
|
|
288
|
+
ok("role" not in disk_role(root, "k6"), "D5 密文节点不写")
|
|
289
|
+
ok(idx_role(root, "k1") == "knowledge",
|
|
290
|
+
"D6 role 进索引(覆盖率判据据此可算)")
|
|
291
|
+
ok(idx_role(root, "k3") is None, "D7 未回填者索引也无值(盘/索引一致)")
|
|
292
|
+
|
|
293
|
+
h = GV.history(root, action=GV.ACTION_ROLE)
|
|
294
|
+
rec = [r for r in h["records"] if r["node"] == "k1"][0]
|
|
295
|
+
ok(all(k in rec for k in ("batch", "actor", "write_id", "entry_id", "value",
|
|
296
|
+
"source", "basis", "fm_before", "layer", "rule_id")),
|
|
297
|
+
"D8 留痕字段完整(回滚凭据齐备)")
|
|
298
|
+
ok(rec["fm_before"] == {"role": None, "had_key": False},
|
|
299
|
+
"D9 留痕记下写前态(原本无键)")
|
|
300
|
+
ok(bool(rec["write_id"]) and len(rec["entry_id"]) == 12,
|
|
301
|
+
"D10 write_id / entry_id 齐备(回滚去重与对账凭据)")
|
|
302
|
+
|
|
303
|
+
rep2 = GV.role_apply(root, role_map={"dsh-memory": "knowledge"}, batch="b1")
|
|
304
|
+
ok(rep2["written"] == 0 and rep2["skipped_drift"] == 0,
|
|
305
|
+
"D11 幂等:重跑零写入(已有值在预演面即被 present 拦截)")
|
|
306
|
+
|
|
307
|
+
orig = GV.role_plan
|
|
308
|
+
|
|
309
|
+
def patched(x, **kw):
|
|
310
|
+
r = orig(x, **kw)
|
|
311
|
+
for it in r["items"]: # 预演→执行之间模拟他人改动
|
|
312
|
+
if it["id"] == "k3":
|
|
313
|
+
fm = disk_role(root, "k3")
|
|
314
|
+
fm["role"] = "assistant"
|
|
315
|
+
write_node(root, "k3", fm)
|
|
316
|
+
return r
|
|
317
|
+
|
|
318
|
+
GV.role_plan = patched
|
|
319
|
+
try:
|
|
320
|
+
rep3 = GV.role_apply(root, ids=["k3"], sources=GV.ALL_SOURCES, batch="b2")
|
|
321
|
+
finally:
|
|
322
|
+
GV.role_plan = orig
|
|
323
|
+
ok(rep3["written"] == 0 and rep3["skipped_drift"] == 1,
|
|
324
|
+
"D12 写前重查:预演后被人改动 → 不覆盖(written=%d drift=%d)"
|
|
325
|
+
% (rep3["written"], rep3["skipped_drift"]))
|
|
326
|
+
ok(disk_role(root, "k3")["role"] == "assistant", "D13 漂移节点保持他人值")
|
|
327
|
+
|
|
328
|
+
ok(set(snapshot(root)) >= files0 and snapshot(root).get("contextual/c1.md") == c1_0,
|
|
329
|
+
"D14 零删除:文件集合只增不减、域外节点零触碰")
|
|
330
|
+
with open(os.path.join(root, CF.INDEX_FILE), encoding="utf-8") as f:
|
|
331
|
+
ok(len(json.load(f)["nodes"]) == 7, "D15 治理后节点总数不变(7)")
|
|
332
|
+
|
|
333
|
+
r2 = mkroot(os.path.join(tmp, "d2"), {
|
|
334
|
+
"a1": {"fm": {"tags": ["role:user"]}},
|
|
335
|
+
"a2": {"fm": {"tags": ["role:knowledge"]}},
|
|
336
|
+
"a3": {"fm": {"tags": ["role:developer"]}}})
|
|
337
|
+
p = GV.role_plan(r2)
|
|
338
|
+
eid = [i for i in p["items"] if i["id"] == "a2"][0]["entry_id"]
|
|
339
|
+
rep4 = GV.role_apply(r2, entry_ids=[eid], batch="b3")
|
|
340
|
+
ok(rep4["written"] == 1 and disk_role(r2, "a2")["role"] == "knowledge",
|
|
341
|
+
"D16 entry_ids 工单式精确执行(只写 1 条)")
|
|
342
|
+
ok("role" not in disk_role(r2, "a1") and "role" not in disk_role(r2, "a3"),
|
|
343
|
+
"D17 工单外节点零触碰")
|
|
344
|
+
ok(rep4["plan_remaining"] == 2, "D18 余量如实上报(%d)" % rep4["plan_remaining"])
|
|
345
|
+
ok(rep4["entry_ids"] == [eid], "D19 留痕回执带 entry_id(可对账)")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ---------------------------- E 组:回滚 ----------------------------
|
|
349
|
+
|
|
350
|
+
def phase_e(tmp):
|
|
351
|
+
print("[E] 回滚(还原 / 删键 / 冲突)")
|
|
352
|
+
root = mkroot(os.path.join(tmp, "e"), {
|
|
353
|
+
"a1": {"fm": {"tags": ["role:user"]}},
|
|
354
|
+
"a2": {"fm": {"tags": ["role:knowledge"], "role": ""}},
|
|
355
|
+
"a3": {"fm": {"tags": ["role:developer"]}}})
|
|
356
|
+
had = disk_role(root, "a2")
|
|
357
|
+
ok("role" in had and GV._blank(had.get("role")),
|
|
358
|
+
"E1 空串 role:键在值空 → 视为缺失(键存在性也要还原,%r)" % had.get("role"))
|
|
359
|
+
p0 = GV.role_plan(root)
|
|
360
|
+
ok(p0["targeted"] == 3 and p0["skipped_present"] == 0,
|
|
361
|
+
"E2 空串节点仍入靶面(targeted=%d)" % p0["targeted"])
|
|
362
|
+
base = GV.role_stats(root)["role_ratio_kn"]
|
|
363
|
+
|
|
364
|
+
rep = GV.role_apply(root, batch="e1", actor="t")
|
|
365
|
+
ok(rep["written"] == 3, "E3 三条写入(%d)" % rep["written"])
|
|
366
|
+
ratios_after = GV.role_stats(root)["role_ratio_kn"]
|
|
367
|
+
ok(ratios_after > base, "E4 覆盖率上升(%.4f → %.4f)" % (base, ratios_after))
|
|
368
|
+
ok(GV.role_stats(root)["by_value"] == {"user": 1, "knowledge": 1, "developer": 1},
|
|
369
|
+
"E5 逐值分布正确(%s)" % GV.role_stats(root)["by_value"])
|
|
370
|
+
|
|
371
|
+
fm = disk_role(root, "a1") # 人工覆盖 → 回滚必须让路
|
|
372
|
+
fm["role"] = "assistant"
|
|
373
|
+
write_node(root, "a1", fm)
|
|
374
|
+
rb = GV.role_rollback(root, batch="e1", actor="t")
|
|
375
|
+
ok(rb["reverted"] == 2 and rb["conflict"] == 1,
|
|
376
|
+
"E6 人工改动者计 conflict 不覆盖(reverted=%d conflict=%d)"
|
|
377
|
+
% (rb["reverted"], rb["conflict"]))
|
|
378
|
+
ok(disk_role(root, "a1")["role"] == "assistant", "E7 冲突节点保持人工值")
|
|
379
|
+
ok("role" not in disk_role(root, "a3"), "E8 原本无键 → 删键还原(不写空串)")
|
|
380
|
+
ok("role" in disk_role(root, "a2") and GV._blank(disk_role(root, "a2").get("role")),
|
|
381
|
+
"E9 原本空值 → 还原空值且键仍在(%r)" % disk_role(root, "a2").get("role"))
|
|
382
|
+
ok(idx_role(root, "a3") is None, "E10 索引随回滚同步(role 清空)")
|
|
383
|
+
|
|
384
|
+
rb2 = GV.role_rollback(root, batch="e1", actor="t")
|
|
385
|
+
ok(rb2["reverted"] == 0 and rb2["skipped_done"] == 2 and rb2["conflict"] == 1,
|
|
386
|
+
"E11 重复回滚凭 write_id 去重(skipped_done=%d)" % rb2["skipped_done"])
|
|
387
|
+
ok(GV.role_stats(root)["by_value"] == {"assistant": 1},
|
|
388
|
+
"E12 回滚后仅剩人工值(%s)" % GV.role_stats(root)["by_value"])
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# ---------------------------- F 组:CLI(fail-closed)----------------------------
|
|
392
|
+
|
|
393
|
+
def run_cli(argv):
|
|
394
|
+
out, err = io.StringIO(), io.StringIO()
|
|
395
|
+
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
|
|
396
|
+
rc = GV.main(argv)
|
|
397
|
+
return rc, out.getvalue(), err.getvalue()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def phase_f(tmp):
|
|
401
|
+
print("[F] CLI(fail-closed)")
|
|
402
|
+
root = mkroot(os.path.join(tmp, "f"), base_nodes())
|
|
403
|
+
env_bak = os.environ.pop("MDCG_ROOT", None)
|
|
404
|
+
try:
|
|
405
|
+
ok(run_cli(["plan", "--root", root])[0] == 0, "F1 plan 退出码 0")
|
|
406
|
+
ok(run_cli(["plan"])[0] == 2, "F2 无 root → 2(fail-closed)")
|
|
407
|
+
ok(run_cli([])[0] == 2, "F3 无子命令 → 2(print_help)")
|
|
408
|
+
ok(run_cli(["apply", "--root", root])[0] == 2,
|
|
409
|
+
"F4 apply 无 --yes → 2(写盘须显式确认)")
|
|
410
|
+
ok(run_cli(["rollback", "--root", root])[0] == 2,
|
|
411
|
+
"F5 rollback 无范围 → 2(防全量误回滚)")
|
|
412
|
+
ok(run_cli(["plan", "--root", root, "--sources", "tags,bogus"])[0] == 2,
|
|
413
|
+
"F6 来源档域外 → 2(不静默降级)")
|
|
414
|
+
ok(run_cli(["plan", "--root", root, "--layer", "contextual"])[0] == 2,
|
|
415
|
+
"F7 跨层收窄 → 2(异常不得裸奔)")
|
|
416
|
+
|
|
417
|
+
rc, out, err = run_cli(["apply", "--root", root, "--yes", "--batch", "cli1",
|
|
418
|
+
"--sources", "tags,map,layer_default"])
|
|
419
|
+
ok(rc == 0 and GV.LAYER_DEFAULT_WARNING[:14] in err,
|
|
420
|
+
"F8 layer_default 开启时 stderr 透出终裁提示")
|
|
421
|
+
ok(disk_role(root, "k3")["role"] == "knowledge", "F9 CLI apply 生效")
|
|
422
|
+
ok(GV.history(root, batch="cli1")["total"] == 3,
|
|
423
|
+
"F10 CLI 留痕按批次可查(%d)" % GV.history(root, batch="cli1")["total"])
|
|
424
|
+
|
|
425
|
+
rc, out, _e = run_cli(["plan", "--root", root, "--ids", "k1", "--json"])
|
|
426
|
+
ok(rc == 0 and json.loads(out)["nodes_scanned"] == 1,
|
|
427
|
+
"F11 plan --json 可机读(M4 报告引用面)")
|
|
428
|
+
rc, out, _e = run_cli(["history", "--root", root, "--json"])
|
|
429
|
+
ok(rc == 0 and json.loads(out)["total"] >= 3, "F12 history --json 可机读")
|
|
430
|
+
rc, out, _e = run_cli(["rollback", "--root", root, "--batch", "cli1"])
|
|
431
|
+
ok(rc == 0 and "还原" in out, "F13 CLI rollback 人读摘要可读(%s)" % out.split("\n")[0])
|
|
432
|
+
ok("role" not in disk_role(root, "k3"), "F14 CLI 回滚生效(键被删除)")
|
|
433
|
+
finally:
|
|
434
|
+
if env_bak is not None:
|
|
435
|
+
os.environ["MDCG_ROOT"] = env_bak
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# ---------------------------- G 组:对照口径 ----------------------------
|
|
439
|
+
|
|
440
|
+
def phase_g(tmp):
|
|
441
|
+
print("[G] 对照口径(复用 conformance,不复刻判据)")
|
|
442
|
+
root = mkroot(os.path.join(tmp, "g"), base_nodes())
|
|
443
|
+
st = GV.role_stats(root)
|
|
444
|
+
ok(st["threshold"] == float(CF.THRESHOLDS["role_coverage_min"]),
|
|
445
|
+
"G1 阈值直接取自 conformance(%s)" % st["threshold"])
|
|
446
|
+
mine = CF._coverage_metrics(CF.load_index(root)).get("role_ratio_kn")
|
|
447
|
+
ok(st["role_ratio_kn"] == mine, "G2 覆盖率与 conformance 逐位一致(%s)" % mine)
|
|
448
|
+
ok(st["target_met"] is False and st["gap_to_target"] > 0,
|
|
449
|
+
"G3 未达标时如实上报缺口(%.4f)" % st["gap_to_target"])
|
|
450
|
+
|
|
451
|
+
GV.role_apply(root, role_map={"dsh-memory": "knowledge"}, batch="g1")
|
|
452
|
+
st2 = GV.role_stats(root)
|
|
453
|
+
ok(st2["role_ratio_kn"] > st["role_ratio_kn"],
|
|
454
|
+
"G4 治理后覆盖率变化可量化(%.4f → %.4f)"
|
|
455
|
+
% (st["role_ratio_kn"], st2["role_ratio_kn"]))
|
|
456
|
+
ok(st2["by_layer"].get("knowledge") == 6,
|
|
457
|
+
"G5 逐层分布(knowledge=%s)" % st2["by_layer"].get("knowledge"))
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
GV.run(root, "role_bogus")
|
|
461
|
+
ok(False, "G6 未知 action → ValueError")
|
|
462
|
+
except ValueError:
|
|
463
|
+
ok(True, "G6 未知 action → ValueError(入口防呆)")
|
|
464
|
+
ok(GV.run(root, "role", apply=False)["dry_run"] is True,
|
|
465
|
+
"G7 run(apply=False) 走预演(默认不写)")
|
|
466
|
+
ok(GV.run(root, "role_stats")["action"] == "role_stats", "G8 run 分发到对照")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# ---------------------------- 入口 ----------------------------
|
|
470
|
+
|
|
471
|
+
def main():
|
|
472
|
+
tmp = tempfile.mkdtemp(prefix="m4_govern_")
|
|
473
|
+
try:
|
|
474
|
+
for fn in (phase_a, phase_b, phase_c, phase_d, phase_e, phase_f, phase_g):
|
|
475
|
+
fn(tmp)
|
|
476
|
+
finally:
|
|
477
|
+
rm(tmp)
|
|
478
|
+
print("\nPASS %d FAIL %d SKIP %d" % (PASS, FAIL, SKIP))
|
|
479
|
+
for f in FAILS:
|
|
480
|
+
print(" - %s" % f)
|
|
481
|
+
return 1 if FAIL else 0
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
if __name__ == "__main__":
|
|
485
|
+
sys.exit(main())
|
package/md_cg/test_p1.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""md 认知图 P1 验收 · 白箱认知架构 9 维度补全
|
|
3
3
|
|
|
4
|
-
覆盖(对照 docs/认知图_MD目录方案_v0.1.md v0.3 待办 + 白箱 5 篇):
|
|
4
|
+
覆盖(对照 docs/mdcg/认知图_MD目录方案_v0.1.md v0.3 待办 + 白箱 5 篇):
|
|
5
5
|
1. MARKS 5 要素 + 验证基底 + 不适用条件
|
|
6
6
|
2. 负记忆目录(rejected/unresolved)
|
|
7
7
|
3. 资格判定四态(ACCEPT/REJECT/DEFER/BLINDSPOT)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""md_cg · 第 11 篇:节点间自动冲突检测(三级决策:情绪 → 反思 → 递归反思)
|
|
3
3
|
|
|
4
4
|
理论出处(本仓原文):
|
|
5
|
-
· 情绪 = 信息差二阶变化 d²D/dt²(`docs/智能的公理化基石.md` §十一)
|
|
5
|
+
· 情绪 = 信息差二阶变化 d²D/dt²(`docs/theory/智能的公理化基石.md` §十一)
|
|
6
6
|
—— L0,独立通道,**不参与信任计算**
|
|
7
7
|
· 反题 = 预测与事实冲突(同文档 :529,条件论七操作)—— L1 条件级冲突检测
|
|
8
8
|
· 递归受深度/节点数/循环/信息增益门槛约束(同文档 :273)—— L2 递归反思
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"""md_cg · 第 12 篇:独立元认知(观察自身认知的二阶单元)
|
|
3
3
|
|
|
4
4
|
理论出处(本仓原文):
|
|
5
|
-
· 情绪 = 信息差二阶变化 d²D/dt²(`docs/智能的公理化基石.md` §十一,:412-510)
|
|
5
|
+
· 情绪 = 信息差二阶变化 d²D/dt²(`docs/theory/智能的公理化基石.md` §十一,:412-510)
|
|
6
6
|
· 情感 = 信任二阶变化 d²T/dt²(同文档 :469-508)
|
|
7
7
|
· 五大单元:元认知是反思/验证通道的**外部观察者**(同文档 :540-556)
|
|
8
8
|
· 推论三「局部不可知」:盲区即知识(同文档 :277-288)
|
|
9
9
|
· 信任 P_trust / P_gap(同文档 :393-408)
|
|
10
|
-
· 导航税:显式状态记录(`docs/智能的认知过程.md`:84-86)
|
|
10
|
+
· 导航税:显式状态记录(`docs/theory/智能的认知过程.md`:84-86)
|
|
11
11
|
|
|
12
12
|
覆盖:
|
|
13
13
|
A 轨迹:D(t) / dD/dt / d²D/dt² / 情绪 / 四态分布
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""md_cg · 第 16 篇:自我状态层(薄自我 + 富索引)
|
|
3
3
|
|
|
4
4
|
理论出处(本仓原文):
|
|
5
|
-
· `docs/灵枢_自我层定义.md:6`:SELF 层 = 身份 + 价值观 + 图接口,
|
|
5
|
+
· `docs/mdcg/灵枢_自我层定义.md:6`:SELF 层 = 身份 + 价值观 + 图接口,
|
|
6
6
|
不可遗忘、跨会话自动加载,且**是薄的**(不该变成事件仓库)。
|
|
7
7
|
· 智能论 §十一:情绪 = 信息差二阶 d^2D/dt^2;§十:P_gap / P_trust;
|
|
8
8
|
情感 = 信任二阶 d^2T/dt^2。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""代码索引 + 回读端到端测试(P26 · 认知图的代码面)。
|
|
3
3
|
|
|
4
|
-
R1 改造的验收(对照 docs/认知图_索引与工程规范化_计划_v0.1.md):
|
|
4
|
+
R1 改造的验收(对照 docs/mdcg/认知图_索引与工程规范化_计划_v0.1.md):
|
|
5
5
|
|
|
6
6
|
① 渲染即 CCG:`codeindex.render` 必须产出 CCG 6 行。否则
|
|
7
7
|
`judge_qualification` 第一步的 ccg_completeness 会直接判 BLINDSPOT ——
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""条件文档图端到端测试(P27 · 认知图的文档面)。
|
|
3
3
|
|
|
4
|
-
R2 改造的验收(对照 docs/认知图_索引与工程规范化_计划_v0.1.md §六 R2、§七):
|
|
4
|
+
R2 改造的验收(对照 docs/mdcg/认知图_索引与工程规范化_计划_v0.1.md §六 R2、§七):
|
|
5
5
|
|
|
6
6
|
① 切块纪律:只切 level<=3;直接正文 <200 字且无子节的小节**合并进父节**
|
|
7
7
|
(不单独建节点,其文字进父节摘要)。既不过细,也不丢内容。
|
|
@@ -41,7 +41,7 @@ FAILS = []
|
|
|
41
41
|
_BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
42
42
|
ROOT = os.path.join(_BASE, "_md_cg_p27")
|
|
43
43
|
DOCS = os.path.join(_BASE, "docs")
|
|
44
|
-
PLAN_DOC = "
|
|
44
|
+
PLAN_DOC = "mdcg/认知图_MD目录方案_v0.1.md"
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def check(name, cond, detail=""):
|
|
@@ -494,6 +494,26 @@ def main():
|
|
|
494
494
|
check("清退后悬空归零", not (ck3.get("dangling") or []),
|
|
495
495
|
str(ck3.get("dangling"))[:130])
|
|
496
496
|
|
|
497
|
+
# 跨进程一致性:新实例重放 _index_log 后,已清退节点不得复活成幽灵条目
|
|
498
|
+
# (索引有条目、文件不存在)——只有把「删除」也写进增量日志才成立。
|
|
499
|
+
cg2 = MdCGOS(ROOT)
|
|
500
|
+
revived = sorted(n for n in ids4 if n in (cg2.index.get("nodes") or {}))
|
|
501
|
+
check("清退后重开不复活幽灵条目", not revived, f"复活={revived}")
|
|
502
|
+
|
|
503
|
+
# 存量幽灵(历史「删除只摘内存索引」遗留):索引有条目、节点文件不存在
|
|
504
|
+
# 也必须清得掉,否则 dangling 永远够不着零。
|
|
505
|
+
gid = "doc_ghost_probe0001"
|
|
506
|
+
cg.index["nodes"][gid] = {"path": "knowledge/ghost_probe.md",
|
|
507
|
+
"layer": "knowledge", "tags": ["doc"],
|
|
508
|
+
"bucket": None, "importance": 0.5}
|
|
509
|
+
g1 = call_tool(cg, "cg", {"op": "ref", "action": "prune"})
|
|
510
|
+
check("幽灵条目被清退(索引有、文件无)",
|
|
511
|
+
len(g1.get("ghost_pruned") or []) >= 1
|
|
512
|
+
and gid not in (cg.index.get("nodes") or {}), str(g1)[:130])
|
|
513
|
+
ck4 = call_tool(cg, "cg", {"op": "ref", "action": "check", "max_nodes": 5000})
|
|
514
|
+
check("清幽灵后悬空仍为零", not (ck4.get("dangling") or []),
|
|
515
|
+
str(ck4.get("dangling"))[:130])
|
|
516
|
+
|
|
497
517
|
finally:
|
|
498
518
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
499
519
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""会话层 + 摄取分派 + 全库导出(P29 · P0 工程缺口验收)。
|
|
3
3
|
|
|
4
|
-
对照 `docs/灵枢82工具_功能整理与迁移映射_v0.1.md` §五:P0 新增三个 op。
|
|
4
|
+
对照 `docs/mdcg/灵枢82工具_功能整理与迁移映射_v0.1.md` §五:P0 新增三个 op。
|
|
5
5
|
|
|
6
6
|
① op 三处同步:`tokens.ALL_OPS` == `cg` 工具 op 描述 == `_cg_call` 分支
|
|
7
7
|
② session:note 幂等写入;recall 一次返回可续接上下文包;compact 启发式压缩
|
package/md_cg/test_p2_mcp.py
CHANGED
|
@@ -262,12 +262,18 @@ def main():
|
|
|
262
262
|
kn = [t["name"] for t in kt]
|
|
263
263
|
check("kernel 只暴露 2 个基元", kn == ["cg", "stg"], str(kn))
|
|
264
264
|
info = kc.call("cg", {"op": "info"})
|
|
265
|
+
# 注:新增 content_kind(如 ccg_marks)须同步本清单——工具面自描述
|
|
266
|
+
# 与 audit.CONTENT_KINDS 真源逐类对齐,防「真源扩了、自描述没跟」。
|
|
265
267
|
check("cg info 自描述审核体系",
|
|
266
268
|
info.get("surface") == "kernel"
|
|
267
269
|
and set(info.get("audit_kinds", {})) == {
|
|
268
270
|
"code", "image_desc", "text", "permission",
|
|
269
|
-
"work_done", "work_wip"},
|
|
271
|
+
"work_done", "work_wip", "ccg_marks"},
|
|
270
272
|
str(list(info.get("audit_kinds", {})))[:120])
|
|
273
|
+
check("cg info 自描述 CCG 契约角色(裁定 B)",
|
|
274
|
+
info.get("ccg_contract", {}).get("生效条件") == "前置条件 precondition"
|
|
275
|
+
and len(info.get("ccg_contract", {})) == 6,
|
|
276
|
+
str(info.get("ccg_contract"))[:120])
|
|
271
277
|
w = kc.call("cg", {"op": "write", "content_kind": "text",
|
|
272
278
|
"content": "hello", "node_id": "kw1"})
|
|
273
279
|
check("未验证的写入进审核队列",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""层归位(P38 · G4 设计态验收)。
|
|
3
3
|
|
|
4
|
-
对照 `docs/认知图_G4-G8缺口裁定单_v0.1.md` §二:批次流水账(`note_`/`milestone_`/
|
|
4
|
+
对照 `docs/mdcg/认知图_G4-G8缺口裁定单_v0.1.md` §二:批次流水账(`note_`/`milestone_`/
|
|
5
5
|
`retest6_`)与感知产物(`imgpart_`/`vpipe_`)混在 knowledge 层,语义上属**情境**,
|
|
6
6
|
需归位到 `contextual` 且**保留可召回**。
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""视觉证据回填(P39 · G5 设计态验收)。
|
|
3
3
|
|
|
4
|
-
对照 `docs/认知图_G4-G8缺口裁定单_v0.1.md` §三:视觉节点证据面恒为 `{}`,
|
|
4
|
+
对照 `docs/mdcg/认知图_G4-G8缺口裁定单_v0.1.md` §三:视觉节点证据面恒为 `{}`,
|
|
5
5
|
根因是产出侧只取 `model_evidence`(白箱部件不带该键)——即**证据口径未定义**。
|
|
6
6
|
本轮以**库外只读归档**(逐部件结构化结果)为证据源,按三档映射回填,脱敏、
|
|
7
7
|
不读图像、不重跑视觉。
|