@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,343 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""conformance 断言集自测(Pi⑤)。
|
|
3
|
+
|
|
4
|
+
纪律(吸取 test_p27【9】教训):**自备数据源,不依赖仓库外目录状态**——
|
|
5
|
+
外部 clone(无 docs/experiments、无真源库)下必须同样全绿。故:
|
|
6
|
+
|
|
7
|
+
* 主测面 = `tempfile` 内**合成库**(确定性、可注入缺陷)→ 验证「注入即检出」;
|
|
8
|
+
* 真源库只在**存在时**做一次集成基线复核(不存在则 SKIP,不报 FAIL)。
|
|
9
|
+
|
|
10
|
+
覆盖:
|
|
11
|
+
A 合成库健康态 → verdict 判定 + strict 语义
|
|
12
|
+
B 五条不变量 fail-closed:类型空间封闭 / 边目标可解析 / 索引↔盘一致 /
|
|
13
|
+
层-目录一致 / 重复度上限(各注入缺陷一次)
|
|
14
|
+
C 边键规范 / 边类型声明 → **WARN 非 FAIL**(读侧兼容,不冒充硬失败)
|
|
15
|
+
D 健康指标 BLINDSPOT(样本不足时不冒充 PASS)
|
|
16
|
+
E G3 unanalyzed 派生判据(四类排除)
|
|
17
|
+
F 基线不劣化:劣化→FAIL、持平→ok
|
|
18
|
+
G 零写入(跑一遍 check 后全库指纹逐字节不变)
|
|
19
|
+
H CLI:坏库 BLINDSPOT 退出码 2 / FAIL 退出码 1 / --json / --baseline / --strict
|
|
20
|
+
I G1 结构完整性(5 空间声明 + 方向分歧 + 文档滞后登记)
|
|
21
|
+
J sustain 挂点(只读、异常不炸、status 透出)
|
|
22
|
+
|
|
23
|
+
运行:python -m md_cg.test_conformance
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import io
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import shutil
|
|
32
|
+
import sys
|
|
33
|
+
import tempfile
|
|
34
|
+
from contextlib import redirect_stdout
|
|
35
|
+
|
|
36
|
+
from . import conformance as C
|
|
37
|
+
|
|
38
|
+
PASS = FAIL = 0
|
|
39
|
+
FAILS = []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ok(cond, label):
|
|
43
|
+
global PASS, FAIL
|
|
44
|
+
if cond:
|
|
45
|
+
PASS += 1
|
|
46
|
+
else:
|
|
47
|
+
FAIL += 1
|
|
48
|
+
FAILS.append(label)
|
|
49
|
+
print(f" FAIL {label}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------- 合成库 ----------------------------
|
|
53
|
+
|
|
54
|
+
def node(nid, layer="knowledge", **kw):
|
|
55
|
+
rec = {"id": nid, "layer": layer, "path": f"{layer}/{nid}.md",
|
|
56
|
+
"content": f"内容 {nid}", "content_hash": f"hash_{nid}", "tags": []}
|
|
57
|
+
rec.update(kw)
|
|
58
|
+
return rec
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
DEFAULT_ACCESS = ({"t": 1, "ids": ["n1", "n2"], "tier": "core"},)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build(root, nodes, *, decisions=None, inbox=None, access=DEFAULT_ACCESS, audit=(),
|
|
65
|
+
write_files=True):
|
|
66
|
+
os.makedirs(root, exist_ok=True)
|
|
67
|
+
with open(os.path.join(root, "_index.json"), "w", encoding="utf-8") as f:
|
|
68
|
+
json.dump({"nodes": nodes}, f, ensure_ascii=False)
|
|
69
|
+
hp = os.path.join(root, "hippocampus")
|
|
70
|
+
os.makedirs(hp, exist_ok=True)
|
|
71
|
+
for name, rows in (("decisions.jsonl", decisions), ("inbox.jsonl", inbox)):
|
|
72
|
+
with open(os.path.join(hp, name), "w", encoding="utf-8") as f:
|
|
73
|
+
for r in rows or []:
|
|
74
|
+
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
|
75
|
+
with open(os.path.join(root, "_access.log"), "w", encoding="utf-8") as f:
|
|
76
|
+
for r in access:
|
|
77
|
+
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
|
78
|
+
with open(os.path.join(root, "_audit.jsonl"), "w", encoding="utf-8") as f:
|
|
79
|
+
for r in audit:
|
|
80
|
+
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
|
81
|
+
if write_files:
|
|
82
|
+
for r in nodes.values():
|
|
83
|
+
if not r.get("path"):
|
|
84
|
+
continue
|
|
85
|
+
p = os.path.join(root, r["path"])
|
|
86
|
+
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
87
|
+
with open(p, "w", encoding="utf-8") as f:
|
|
88
|
+
f.write(r.get("content", ""))
|
|
89
|
+
return root
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def dec(n, decision="accept"):
|
|
93
|
+
return [{"pid": f"p{i}", "decision": decision} for i in range(n)]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def failing(rep):
|
|
97
|
+
return [c["id"] for c in rep["checks"] if c["level"] == "FAIL" and not c["ok"]]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def warn_no(rep):
|
|
101
|
+
return [c["id"] for c in rep["checks"] if c["level"] == "WARN" and not c["ok"]]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def level_of(rep, cid):
|
|
105
|
+
return next((c["level"] for c in rep["checks"] if c["id"] == cid), None)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def snapshot(root):
|
|
109
|
+
out = {}
|
|
110
|
+
for dp, _, fns in os.walk(root):
|
|
111
|
+
for fn in fns:
|
|
112
|
+
p = os.path.join(dp, fn)
|
|
113
|
+
with open(p, "rb") as f:
|
|
114
|
+
out[os.path.relpath(p, root)] = hashlib.md5(f.read()).hexdigest()
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# 健康态:knowledge 层三节点 role/basis/evidence 齐备(判据=knowledge 层口径),
|
|
119
|
+
# 混层 1/3=33% < 60% 上限,access 覆盖 2/5=40% ≥ 30% 下限,闸门样本 25 ≥ 20
|
|
120
|
+
HEALTHY = {
|
|
121
|
+
"n1": node("n1", tags=["doc:note"], role="knowledge-card",
|
|
122
|
+
verification_basis="test", evidence_count=2),
|
|
123
|
+
"n2": node("n2", role="knowledge-card", verification_basis="test",
|
|
124
|
+
evidence_count=3, lifecycle_state="active"),
|
|
125
|
+
"n3": node("n3", layer="contextual"),
|
|
126
|
+
"n4": node("n4", layer="self"),
|
|
127
|
+
"n5": node("n5", role="knowledge-card", verification_basis="textbook"),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main():
|
|
132
|
+
print("== test_conformance: 合成库主测面 ==")
|
|
133
|
+
tmp = tempfile.mkdtemp(prefix="conf_")
|
|
134
|
+
try:
|
|
135
|
+
# ---------- A 健康态 ----------
|
|
136
|
+
r1 = build(os.path.join(tmp, "healthy"), dict(HEALTHY), decisions=dec(25))
|
|
137
|
+
rep = C.check(r1)
|
|
138
|
+
ok(rep["verdict"] == "PASS", f"A1 健康库 verdict=PASS(实得 {rep['verdict']})")
|
|
139
|
+
ok(not failing(rep), f"A2 健康库无 FAIL(实得 {failing(rep)})")
|
|
140
|
+
ok(level_of(rep, "stratum.role_coverage") == "BLINDSPOT",
|
|
141
|
+
"A3 样本不足 → 比率指标 BLINDSPOT")
|
|
142
|
+
rep_s = C.check(r1, strict=True)
|
|
143
|
+
ok(rep_s["verdict"] != "PASS", "A4 strict 下 BLINDSPOT 不算 PASS")
|
|
144
|
+
ok(C.check(r1)["counts"] == rep["counts"], "A5 复跑确定性(计数一致)")
|
|
145
|
+
|
|
146
|
+
# ---------- B 不变量 fail-closed ----------
|
|
147
|
+
b1 = dict(HEALTHY)
|
|
148
|
+
b1["bad"] = node("bad", layer="不存在的层")
|
|
149
|
+
r2 = build(os.path.join(tmp, "b1"), b1, decisions=dec(25))
|
|
150
|
+
rep = C.check(r2)
|
|
151
|
+
ok("enum.layer.closed" in failing(rep), "B1 层越界 → FAIL enum.layer.closed")
|
|
152
|
+
ok(rep["verdict"] == "FAIL", "B2 有 FAIL → verdict=FAIL")
|
|
153
|
+
|
|
154
|
+
b2 = dict(HEALTHY)
|
|
155
|
+
b2["n1"] = node("n1", edges=[{"relation_type": "part_of", "target": "ghost"}])
|
|
156
|
+
r3 = build(os.path.join(tmp, "b2"), b2, decisions=dec(25))
|
|
157
|
+
ok("edge.target.resolvable" in failing(C.check(r3)),
|
|
158
|
+
"B3 悬空边 → FAIL edge.target.resolvable")
|
|
159
|
+
|
|
160
|
+
b3 = dict(HEALTHY)
|
|
161
|
+
b3["n1"] = node("n1", path="contextual/n1.md") # layer=knowledge
|
|
162
|
+
r4 = build(os.path.join(tmp, "b3"), b3, decisions=dec(25))
|
|
163
|
+
ok("index.layer.dir.match" in failing(C.check(r4)),
|
|
164
|
+
"B4 层-目录不一致 → FAIL index.layer.dir.match")
|
|
165
|
+
|
|
166
|
+
b4 = dict(HEALTHY)
|
|
167
|
+
b4["n5"] = node("n5", path="") # path 缺失
|
|
168
|
+
r5 = build(os.path.join(tmp, "b4"), b4, decisions=dec(25))
|
|
169
|
+
ok("index.path.present" in failing(C.check(r5)),
|
|
170
|
+
"B5 path 缺失 → FAIL index.path.present")
|
|
171
|
+
|
|
172
|
+
b5 = dict(HEALTHY)
|
|
173
|
+
b5["n5"] = node("n5", path="knowledge/nowhere.md") # 盘上不存在
|
|
174
|
+
r6 = build(os.path.join(tmp, "b5"), b5, decisions=dec(25), write_files=False)
|
|
175
|
+
ok("index.path.present" in failing(C.check(r6)),
|
|
176
|
+
"B6 盘上文件不存在 → FAIL index.path.present")
|
|
177
|
+
ok(not failing(C.check(r6, check_paths=False)),
|
|
178
|
+
"B7 --no-path-check 跳过查盘后不报该 FAIL")
|
|
179
|
+
|
|
180
|
+
b6 = dict(HEALTHY)
|
|
181
|
+
for i in range(C.DUP_GROUP_MAX + 1):
|
|
182
|
+
b6[f"d{i}"] = node(f"d{i}", content_hash="same")
|
|
183
|
+
r7 = build(os.path.join(tmp, "b6"), b6, decisions=dec(25))
|
|
184
|
+
ok("dup.content.no_blowup" in failing(C.check(r7)),
|
|
185
|
+
f"B8 重复组 > {C.DUP_GROUP_MAX} → FAIL dup.content.no_blowup")
|
|
186
|
+
|
|
187
|
+
# ---------- C 边键/边类型 = WARN ----------
|
|
188
|
+
c1 = dict(HEALTHY)
|
|
189
|
+
c1["n1"] = node("n1", edges=[{"type": "part_of", "target": "n2"},
|
|
190
|
+
{"relation_type": "causal", "target": "n3"}])
|
|
191
|
+
r8 = build(os.path.join(tmp, "c1"), c1, decisions=dec(25))
|
|
192
|
+
rep = C.check(r8)
|
|
193
|
+
ok(rep["edges"]["non_canonical"] == 1, "C1 legacy type 键计数 =1")
|
|
194
|
+
ok(level_of(rep, "edge.key.canonical") == "WARN", "C2 边键规范判为 WARN")
|
|
195
|
+
ok("edge.key.canonical" not in failing(rep), "C3 边键 legacy 不判 FAIL(读侧兼容)")
|
|
196
|
+
ok("part_of" not in rep["g1"]["edge_type"]["undeclared_used"],
|
|
197
|
+
"C4 已声明边类型不误报")
|
|
198
|
+
|
|
199
|
+
c2 = dict(HEALTHY)
|
|
200
|
+
c2["n1"] = node("n1", edges=[{"relation_type": "bogus_rel", "target": "n2"}])
|
|
201
|
+
r9 = build(os.path.join(tmp, "c2"), c2, decisions=dec(25))
|
|
202
|
+
rep = C.check(r9)
|
|
203
|
+
ok("bogus_rel" in rep["g1"]["edge_type"]["undeclared_used"],
|
|
204
|
+
"C5 未声明边类型被 G1 列出")
|
|
205
|
+
ok(level_of(rep, "edge.rel.declared") == "WARN", "C6 未声明边类型判为 WARN")
|
|
206
|
+
|
|
207
|
+
# ---------- D 数据源缺失 ----------
|
|
208
|
+
r10 = os.path.join(tmp, "d1")
|
|
209
|
+
build(r10, dict(HEALTHY), decisions=dec(25))
|
|
210
|
+
os.remove(os.path.join(r10, "_access.log"))
|
|
211
|
+
rep = C.check(r10)
|
|
212
|
+
ok(level_of(rep, "reach.ratio") == "BLINDSPOT", "D1 触达日志缺失 → BLINDSPOT")
|
|
213
|
+
ok(rep["reach"]["ratio"] is None, "D2 触达率如实置 None 不猜测")
|
|
214
|
+
r11 = os.path.join(tmp, "d2")
|
|
215
|
+
build(r11, dict(HEALTHY), decisions=[])
|
|
216
|
+
ok(level_of(C.check(r11), "gate.sample") == "WARN", "D3 闸门样本不足 → WARN")
|
|
217
|
+
|
|
218
|
+
# ---------- E G3 unanalyzed 派生 ----------
|
|
219
|
+
e1 = {"u1": node("u1"), # 三者皆空 → unanalyzed
|
|
220
|
+
"u2": node("u2", verification_basis="test"), # 有基底 → 非
|
|
221
|
+
"u3": node("u3", evidence_count=2), # 有证据 → 非
|
|
222
|
+
"u4": node("u4", lifecycle_state="converged")} # 有状态 → 非
|
|
223
|
+
r12 = build(os.path.join(tmp, "e1"), e1, decisions=dec(25))
|
|
224
|
+
un = C.check(r12)["unanalyzed"]
|
|
225
|
+
ok(un["count"] == 1 and un["sample"] == ["u1"],
|
|
226
|
+
f"E1 unanalyzed 派生判据(实得 {un['count']})")
|
|
227
|
+
ok(C.check(r12)["coverage"]["nodes"] == 4, "E2 节点计数正确")
|
|
228
|
+
|
|
229
|
+
# ---------- F 基线不劣化 ----------
|
|
230
|
+
rep_ok = C.check(r1)
|
|
231
|
+
m = C.metrics_of(rep_ok)
|
|
232
|
+
base = {"metrics": dict(m)}
|
|
233
|
+
ok(not [c for c in C.check(r1, baseline=base)["checks"]
|
|
234
|
+
if c["id"].startswith("baseline.") and not c["ok"]],
|
|
235
|
+
"F1 与自身基线比对不劣化")
|
|
236
|
+
base_bad = {"metrics": dict(m, non_canonical=0, mixed_ratio=0.0, unanalyzed=0)}
|
|
237
|
+
bad = [c["id"] for c in C.check(r8, baseline=base_bad)["checks"]
|
|
238
|
+
if c["id"].startswith("baseline.") and not c["ok"]]
|
|
239
|
+
ok("baseline.non_canonical" in bad, "F2 非规范边劣化 → FAIL 基线项")
|
|
240
|
+
ok(all(c["id"] in m for c in [{"id": k} for k in
|
|
241
|
+
("mixed_ratio", "non_canonical", "dangling",
|
|
242
|
+
"dup_groups", "unanalyzed")]),
|
|
243
|
+
"F3 比对键 ⊆ metrics_of 输出(无键名漂移)")
|
|
244
|
+
ok("reach_ratio" in m, "F4 触达率入基线存档")
|
|
245
|
+
|
|
246
|
+
# ---------- G 零写入 ----------
|
|
247
|
+
r13 = build(os.path.join(tmp, "g1"), dict(HEALTHY), decisions=dec(25))
|
|
248
|
+
before = snapshot(r13)
|
|
249
|
+
C.check(r13)
|
|
250
|
+
C.check(r13, strict=True)
|
|
251
|
+
C.report_summary(r13)
|
|
252
|
+
after = snapshot(r13)
|
|
253
|
+
ok(before == after, "G1 跑三遍后全库指纹逐字节不变(只读零写入)")
|
|
254
|
+
|
|
255
|
+
# ---------- H CLI ----------
|
|
256
|
+
buf = io.StringIO()
|
|
257
|
+
with redirect_stdout(buf):
|
|
258
|
+
rc_bad = C.main(["--root", os.path.join(tmp, "nonexistent")])
|
|
259
|
+
out = buf.getvalue()
|
|
260
|
+
ok(rc_bad == 2, "H1 坏库退出码 2(BLINDSPOT 非 PASS)")
|
|
261
|
+
ok("BLINDSPOT" in out and "缺失维度" in out, "H2 坏库输出含缺失维度提示")
|
|
262
|
+
with redirect_stdout(io.StringIO()):
|
|
263
|
+
rc_fail = C.main(["--root", r2])
|
|
264
|
+
ok(rc_fail == 1, "H3 verdict=FAIL 退出码 1")
|
|
265
|
+
jp = os.path.join(tmp, "out.json")
|
|
266
|
+
bp = os.path.join(tmp, "base.json")
|
|
267
|
+
with redirect_stdout(io.StringIO()):
|
|
268
|
+
rc_ok = C.main(["--root", r1, "--json", jp, "--write-baseline", bp])
|
|
269
|
+
ok(rc_ok == 0, "H4 健康库退出码 0")
|
|
270
|
+
ok(json.load(open(jp, encoding="utf-8"))["verdict"] == "PASS", "H5 --json 落盘可用")
|
|
271
|
+
ok("metrics" in json.load(open(bp, encoding="utf-8")), "H6 --write-baseline 落盘可用")
|
|
272
|
+
with redirect_stdout(io.StringIO()):
|
|
273
|
+
rc_cmp = C.main(["--root", r1, "--baseline", bp])
|
|
274
|
+
ok(rc_cmp == 0, "H7 --baseline 比对通路可用")
|
|
275
|
+
|
|
276
|
+
# ---------- I G1 结构 ----------
|
|
277
|
+
sp = C.enum_spaces()
|
|
278
|
+
ok(set(sp) == {"layer", "edge_type", "derived_relation",
|
|
279
|
+
"verification_basis", "lifecycle_state"},
|
|
280
|
+
f"I1 五个类型空间齐备(实得 {sorted(sp)})")
|
|
281
|
+
ok(all(v["declared"] and v["source"] for v in sp.values()),
|
|
282
|
+
"I2 每空间都有声明值 + 模块真源")
|
|
283
|
+
ok("knowledge" in sp["layer"]["declared"], "I3 layer 真源含 knowledge")
|
|
284
|
+
g1 = C.check(r1)["g1"]
|
|
285
|
+
ok(set(("_direction_conflicts", "_verified_consistent", "_doc_code_drift",
|
|
286
|
+
"_cross_space_overlap", "_new_type_touchpoints")) <= set(g1),
|
|
287
|
+
"I4 G1 汇总字段齐备")
|
|
288
|
+
ok(g1["role"]["declared"] is None and g1["role"]["closed"] is None,
|
|
289
|
+
"I5 无真源空间(role)标 None→BLINDSPOT 不冒充封闭")
|
|
290
|
+
ok("hierarchical" in [d["literal"] for d in g1["_direction_conflicts"]],
|
|
291
|
+
"I6 方向口径分歧已登记")
|
|
292
|
+
ok(sp["edge_type"]["declared"] and "part_of" in sp["edge_type"]["declared"],
|
|
293
|
+
"I7 边类型真源可用")
|
|
294
|
+
|
|
295
|
+
# ---------- J sustain 挂点 ----------
|
|
296
|
+
from . import sustain as S
|
|
297
|
+
ok(hasattr(S.SustainLoop, "_tick_conformance"), "J1 SustainLoop 有 conformance 挂点")
|
|
298
|
+
loop = S.SustainLoop(type("CG", (), {"root": r1})(), name="t_conf", d=tmp)
|
|
299
|
+
loop._tick_conformance()
|
|
300
|
+
ok(loop.last_conformance["ok"] is True, "J2 挂点跑通且 ok=True")
|
|
301
|
+
ok("last_conformance" in loop.status(), "J3 status 透出结论")
|
|
302
|
+
loop2 = S.SustainLoop(type("CG", (), {"root": os.path.join(tmp, "nope")})(),
|
|
303
|
+
name="t_conf2", d=tmp)
|
|
304
|
+
loop2._tick_conformance()
|
|
305
|
+
ok(loop2.last_conformance["ok"] is False
|
|
306
|
+
and loop2.last_conformance["verdict"] == "BLINDSPOT",
|
|
307
|
+
"J4 坏库不抛异常、如实标 BLINDSPOT")
|
|
308
|
+
ok(C.report_summary(os.path.join(tmp, "nope2"))["verdict"] == "BLINDSPOT",
|
|
309
|
+
"J5 显式坏路径 report_summary 不炸且如实 BLINDSPOT")
|
|
310
|
+
|
|
311
|
+
# ---------- 集成(真源存在才跑,缺失即 SKIP)----------
|
|
312
|
+
real = "D:/Program Files/2_ai/AEIS/data/mdcg"
|
|
313
|
+
if os.path.exists(os.path.join(real, "_index.json")):
|
|
314
|
+
rp = C.check(real, check_paths=False)
|
|
315
|
+
cv = rp["coverage"]
|
|
316
|
+
print(f" [集成] 真源 nodes={rp['nodes']} 混层比={cv['mixed_ratio'] * 100:.1f}%"
|
|
317
|
+
f" dir_mismatch={rp['paths']['layer_mismatch']}"
|
|
318
|
+
f" 闸门={rp['gate']['decisions_total']} verdict={rp['verdict']}")
|
|
319
|
+
ok(rp["nodes"] > 5000, "K1 真源节点数 > 5000")
|
|
320
|
+
ok(0.50 <= cv["mixed_ratio"] <= 0.65,
|
|
321
|
+
f"K2 混层比复现 9-15 基线 58.4%(实得 {cv['mixed_ratio'] * 100:.1f}%)")
|
|
322
|
+
ok(rp["paths"]["layer_mismatch"] == 0, "K3 层-目录一致 = 0(9-15 基线)")
|
|
323
|
+
ok(rp["gate"]["decisions_total"] >= 100, "K4 闸门样本 ≥ 100")
|
|
324
|
+
ok(cv["role_ratio"] < 0.10, "K5 role 覆盖率仍处治理区(<10%)")
|
|
325
|
+
else:
|
|
326
|
+
print(" [集成] SKIP 真源库不存在(外部 clone 正常情形)")
|
|
327
|
+
finally:
|
|
328
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
329
|
+
|
|
330
|
+
print(f"\nPASS={PASS} FAIL={FAIL}")
|
|
331
|
+
if FAILS:
|
|
332
|
+
print("失败项:")
|
|
333
|
+
for f in FAILS:
|
|
334
|
+
print(" -", f)
|
|
335
|
+
return 1
|
|
336
|
+
print("conformance 断言集自测全绿。")
|
|
337
|
+
return 0
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
if __name__ == "__main__":
|
|
341
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
342
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
343
|
+
sys.exit(main())
|
|
@@ -63,6 +63,30 @@ t3, _ = en_normalizer.normalize_en_query("The dogs were running")
|
|
|
63
63
|
ok("the" not in [x.lower() for x in t3] and "were" not in [x.lower() for x in t3],
|
|
64
64
|
"停用词剔除: %s" % t3)
|
|
65
65
|
|
|
66
|
+
# ---- 2b · 置信分层与第三方暴露面修正(2026-09-15,第三方 LoCoMo 报告发现 5)----
|
|
67
|
+
# 置信信号:mapped 词带 source(manual=人工校对层 / cedict=机械反查层)与
|
|
68
|
+
# low_confidence(cedict 层=True——第一义项直译语境失配风险,警告不拒绝)
|
|
69
|
+
t4, d4 = en_normalizer.normalize_en_query("The scavenger hunt was fun")
|
|
70
|
+
ok("寻宝游戏" in t4, "内嵌短语扫描 scavenger hunt→寻宝游戏(长文本内生效): %s" % t4)
|
|
71
|
+
ok(d4 and d4[0].get("action") == "phrase_inline", "phrase_inline 留痕: %s" % d4[:1])
|
|
72
|
+
ok(en_normalizer.normalize_en_query("scared")[0] == ["害怕"],
|
|
73
|
+
"手工层修正 scared→害怕(错切链路 scar×CEDICT 创痕)")
|
|
74
|
+
ok(en_normalizer.normalize_en_query("shelter")[0] == ["收容所"], "手工层修正 shelter→收容所")
|
|
75
|
+
ok(en_normalizer.normalize_en_query("tough")[0] == ["坚强"], "手工层修正 tough→坚强")
|
|
76
|
+
ok(en_normalizer.normalize_en_query("three times a week")[0] == ["每周三次"],
|
|
77
|
+
"短语 three times a week→每周三次")
|
|
78
|
+
ok(en_normalizer.normalize_en_query("times")[0] == ["次"], "手工层修正 times→次")
|
|
79
|
+
t6, d6 = en_normalizer.normalize_en_query("great and thanks")
|
|
80
|
+
lowc = [dd for dd in d6 if dd.get("action") == "mapped"]
|
|
81
|
+
ok(lowc and all(dd.get("source") == "cedict" and dd.get("low_confidence") is True
|
|
82
|
+
for dd in lowc), "CEDICT 机械层命中=低置信: %s" % d6)
|
|
83
|
+
t7, d7 = en_normalizer.normalize_en_query("pottery was great")
|
|
84
|
+
mfd = [dd for dd in d7 if dd.get("orig", "").lower() == "pottery"]
|
|
85
|
+
ok(mfd and mfd[0].get("source") == "manual" and mfd[0].get("low_confidence") is False,
|
|
86
|
+
"人工校对层=高置信+来源透出: %s" % mfd)
|
|
87
|
+
ok(en_normalizer.low_confidence_terms(d6) == ["great", "thanks"],
|
|
88
|
+
"low_confidence_terms 审计提取(去重保序): %s" % en_normalizer.low_confidence_terms(d6))
|
|
89
|
+
|
|
66
90
|
# ---- 3 · zh_en_atoms:中文→标准英文原子序列 ----
|
|
67
91
|
ok(zh_en_atoms.serialize("我昨天吃牛肉") == "i yester day eat cow meat",
|
|
68
92
|
"serialize 复合词拆原子")
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""体检读数规模纪律验证(O(1) 元数据口径,替代全量扫描)。
|
|
3
|
+
|
|
4
|
+
背景(第 4 条取证):审计日志无上界增长(实测 ~2 条/s、累计 ~21.5 M 条 / 4.0 GB),
|
|
5
|
+
而健康度只需要「量级」这一 O(1) 信息。旧实现 `len(list(read_jsonl(p)))` 把只读体检
|
|
6
|
+
绑到 O(n) 全量解析上(4.0 GB → RSS 5 GB+、数十分钟不返回、整条 MCP 通道被一次调用
|
|
7
|
+
堵死);改用流式数行仍要 O(n) 磁盘 IO,随运行时长线性劣化——同属「指标与代价错配」。
|
|
8
|
+
|
|
9
|
+
断言面:
|
|
10
|
+
① 缺失文件:零读数不炸(bytes=0 / events=0 / exact=True)
|
|
11
|
+
② 阈值内:events 精确 == 日志行数(口径与物化读一致,改造不丢读数)
|
|
12
|
+
③ 超阈值:只读元数据 + 尾部估算,exact=False,且**不触发任何全量计数**(O(n) 守卫)
|
|
13
|
+
④ 估算忠于量级:与真实行数相对误差 < 20%(可用但不假装精确)
|
|
14
|
+
⑤ health_os 集成:审计面走 _log_scale,**read_jsonl/count_jsonl 都不得再碰审计路径**
|
|
15
|
+
(防退回物化读/全量扫的核心回归守卫)
|
|
16
|
+
⑥ 口径连续:小审计文件下 health_os 仍给出精确条数 + exact=True
|
|
17
|
+
|
|
18
|
+
运行:python -m md_cg.test_health_scale
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
import tempfile
|
|
26
|
+
|
|
27
|
+
from . import mdcos as mdcos_mod
|
|
28
|
+
from .mdcos import MdCGOS
|
|
29
|
+
|
|
30
|
+
PASS = FAIL = 0
|
|
31
|
+
FAILS = []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def ok(cond, label):
|
|
35
|
+
global PASS, FAIL
|
|
36
|
+
if cond:
|
|
37
|
+
PASS += 1
|
|
38
|
+
else:
|
|
39
|
+
FAIL += 1
|
|
40
|
+
FAILS.append(label)
|
|
41
|
+
print(f" FAIL {label}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _write_log(path, n, pad=100):
|
|
45
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
46
|
+
for i in range(n):
|
|
47
|
+
f.write(json.dumps({"t": float(i), "op": "add", "i": i,
|
|
48
|
+
"pad": "x" * pad}) + "\n")
|
|
49
|
+
return os.path.getsize(path)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _Spy:
|
|
53
|
+
"""记录 mdcos 模块级 read_jsonl / count_jsonl 的调用路径(探针)。"""
|
|
54
|
+
|
|
55
|
+
def __enter__(self):
|
|
56
|
+
self.read, self.count = [], []
|
|
57
|
+
self._read, self._count = mdcos_mod.read_jsonl, mdcos_mod.count_jsonl
|
|
58
|
+
spy = self
|
|
59
|
+
|
|
60
|
+
def _r(p, *a, **k):
|
|
61
|
+
spy.read.append(p)
|
|
62
|
+
return spy._read(p, *a, **k)
|
|
63
|
+
|
|
64
|
+
def _c(p, *a, **k):
|
|
65
|
+
spy.count.append(p)
|
|
66
|
+
return spy._count(p, *a, **k)
|
|
67
|
+
|
|
68
|
+
mdcos_mod.read_jsonl, mdcos_mod.count_jsonl = _r, _c
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __exit__(self, *exc):
|
|
72
|
+
mdcos_mod.read_jsonl, mdcos_mod.count_jsonl = self._read, self._count
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main():
|
|
77
|
+
tmp = tempfile.mkdtemp(prefix="mdcg_scale_")
|
|
78
|
+
try:
|
|
79
|
+
root = os.path.join(tmp, "root")
|
|
80
|
+
os.makedirs(root, exist_ok=True)
|
|
81
|
+
cg = MdCGOS(root)
|
|
82
|
+
audit = cg.audit_log
|
|
83
|
+
|
|
84
|
+
# ---------- ① 缺失文件 ----------
|
|
85
|
+
s = cg._log_scale(os.path.join(root, "_nope.jsonl"))
|
|
86
|
+
ok(s["bytes"] == 0 and s["events"] == 0 and s["exact"] is True,
|
|
87
|
+
"①缺失日志 → 零读数不炸(bytes=0/events=0/exact=True)")
|
|
88
|
+
|
|
89
|
+
# ---------- ② 阈值内精确 ----------
|
|
90
|
+
n = 200
|
|
91
|
+
size = _write_log(audit, n)
|
|
92
|
+
cg.AUDIT_COUNT_MAX_BYTES = size + 1
|
|
93
|
+
s = cg._log_scale(audit)
|
|
94
|
+
ok(s["exact"] is True and s["events"] == n and s["bytes"] == size,
|
|
95
|
+
"②规模在阈值内 → 精确条数(%d 行,与物化读口径一致)" % n)
|
|
96
|
+
|
|
97
|
+
# ---------- ③ 超阈值:O(1),不触发全量计数 ----------
|
|
98
|
+
cg.AUDIT_COUNT_MAX_BYTES = 1024 # 人工压低阈值,等价于 GB 级日志
|
|
99
|
+
with _Spy() as spy:
|
|
100
|
+
s = cg._log_scale(audit)
|
|
101
|
+
ok(s["exact"] is False and s["bytes"] == size,
|
|
102
|
+
"③超阈值 → exact=False,bytes 仍为 O(1) 精确元数据")
|
|
103
|
+
ok(s["events"] is not None and s["events"] > 0,
|
|
104
|
+
"③b超阈值仍给读数(尾部采样估算),不是拒不回答")
|
|
105
|
+
ok(not spy.count and not spy.read,
|
|
106
|
+
"③c超阈值分支零全量调用(未 count_jsonl / 未 read_jsonl)——O(n) 守卫")
|
|
107
|
+
|
|
108
|
+
# ---------- ④ 估算忠于量级 ----------
|
|
109
|
+
rel = abs(s["events"] - n) / float(n)
|
|
110
|
+
ok(rel < 0.20,
|
|
111
|
+
"④估算与真实行数相对误差 %.1f%% < 20%%(量级可用,不假装精确)" % (rel * 100))
|
|
112
|
+
|
|
113
|
+
# ---------- ⑤ health_os 集成:审计路径零全量读 ----------
|
|
114
|
+
with _Spy() as spy:
|
|
115
|
+
h = cg.health_os()
|
|
116
|
+
o = h["os"]
|
|
117
|
+
ok(o["audit_events_exact"] is False and o["audit_bytes"] == size
|
|
118
|
+
and o["audit_events"] == s["events"],
|
|
119
|
+
"⑤health_os 透出审计量级(exact=False/bytes 精确/events=估算)")
|
|
120
|
+
ok(not any("_audit.jsonl" in p for p in spy.read),
|
|
121
|
+
"⑤bhealth_os 不再对审计日志做物化读(read_jsonl 未被以审计路径调用)")
|
|
122
|
+
ok(not any("_audit.jsonl" in p for p in spy.count),
|
|
123
|
+
"⑤chealth_os 不再对审计日志做全量计数(count_jsonl 未被以审计路径调用)")
|
|
124
|
+
|
|
125
|
+
# ---------- ⑥ 口径连续:小日志仍精确 ----------
|
|
126
|
+
cg.AUDIT_COUNT_MAX_BYTES = 64 << 20
|
|
127
|
+
root2 = os.path.join(tmp, "root2")
|
|
128
|
+
os.makedirs(root2, exist_ok=True)
|
|
129
|
+
cg2 = MdCGOS(root2)
|
|
130
|
+
_write_log(cg2.audit_log, 37)
|
|
131
|
+
o2 = cg2.health_os()["os"]
|
|
132
|
+
ok(o2["audit_events"] == 37 and o2["audit_events_exact"] is True,
|
|
133
|
+
"⑥小日志 → health_os 仍给精确条数(37 行,改造不丢读数)")
|
|
134
|
+
|
|
135
|
+
# ---------- ⑦ 真实写入风格:流式行数 == 物化条数(口径等价) ----------
|
|
136
|
+
import random
|
|
137
|
+
|
|
138
|
+
from .fsutil import append_jsonl, read_jsonl
|
|
139
|
+
real = os.path.join(root, "_real.jsonl")
|
|
140
|
+
for i in range(50):
|
|
141
|
+
append_jsonl(real, {"i": i, "pad": "y" * random.randint(1, 80)})
|
|
142
|
+
n_real = len(list(read_jsonl(real)))
|
|
143
|
+
s_real = cg._log_scale(real)
|
|
144
|
+
ok(s_real["events"] == n_real == 50 and s_real["exact"] is True,
|
|
145
|
+
"⑦完整写入日志:流式行数 %s == 物化条数 %d(口径逐位相等)"
|
|
146
|
+
% (s_real["events"], n_real))
|
|
147
|
+
os.remove(real)
|
|
148
|
+
|
|
149
|
+
# ---------- ⑧ 半截尾行边界:下界口径,最多差 1 行 ----------
|
|
150
|
+
bad = os.path.join(root, "_bad.jsonl")
|
|
151
|
+
with open(bad, "w", encoding="utf-8") as f:
|
|
152
|
+
f.write('{"ok": 1}\nnot-json\n{"ok": 2}') # 末行未终止 + 非法 JSON
|
|
153
|
+
sb = cg._log_scale(bad)
|
|
154
|
+
n_material = len(list(read_jsonl(bad)))
|
|
155
|
+
ok(sb["events"] == 2 and sb["exact"] is True,
|
|
156
|
+
"⑧半截尾行不计入(换行数 2 vs 物化条数 %d)——下界口径,差 1 行在量级容差内"
|
|
157
|
+
% n_material)
|
|
158
|
+
os.remove(bad)
|
|
159
|
+
|
|
160
|
+
cg.close()
|
|
161
|
+
cg2.close()
|
|
162
|
+
finally:
|
|
163
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
164
|
+
|
|
165
|
+
print(f"\nhealth_scale: {PASS} pass / {FAIL} fail")
|
|
166
|
+
if FAILS:
|
|
167
|
+
for f in FAILS:
|
|
168
|
+
print(f" - {f}")
|
|
169
|
+
raise SystemExit(1 if FAIL else 0)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|
|
@@ -17,6 +17,12 @@ import os
|
|
|
17
17
|
|
|
18
18
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
19
19
|
|
|
20
|
+
# Windows cmd 默认 GBK 代码页:带圈数字 ⑪⑫⑬ 等不在 GBK 内,打印即
|
|
21
|
+
# UnicodeEncodeError,且崩在断言之后、报告之前 —— 同一测试「因环境而异」。
|
|
22
|
+
# 测试自带 UTF-8 兜底,不依赖调用方记得加 -X utf8(可复现性纪律)。
|
|
23
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
24
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
25
|
+
|
|
20
26
|
from md_cg.mdcg import MdCG
|
|
21
27
|
from md_cg.mdcos import MdCGSecure
|
|
22
28
|
from md_cg.security import DEFAULT_SENSITIVITY, Principal
|