@furongjun1999/dsh-memory 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +588 -572
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/discipline/harnesses.yaml +31 -0
- package/docs/discipline/templates/full.md.tmpl +1 -1
- package/docs/discipline/templates/rules.mdc.tmpl +1 -1
- package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +418 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +441 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +398 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +498 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +669 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +683 -0
- package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +205 -0
- package/docs/eval//346/200/247/350/203/275/344/270/223/351/241/271_/345/206/267/346/237/245/350/257/242/344/270/216/345/206/205/345/255/230_v1.0.md +218 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +10 -0
- package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +646 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v20.md +283 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v21.md +224 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v22.md +223 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v23.md +293 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v24.md +303 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v25.md +230 -0
- package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +20 -0
- package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +12 -0
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +21 -9
- package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +7 -2
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +39 -3
- package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +220 -0
- package/docs/plans//347/234/237/346/272/220/347/264/242/345/274/225_/351/200/232/347/224/250/346/234/272/345/210/266_v0.3.md +227 -0
- package/docs/plans//350/234/202/345/267/242/346/250/241/345/236/213/345/257/206/351/222/245/351/205/215/347/275/256/351/235/242_v1.0.md +385 -0
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +460 -460
- package/lib/bridge.js +24 -2
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.js +16 -1
- package/lib/lib/prompt_safety.d.ts +52 -1
- package/lib/lib/prompt_safety.js +76 -1
- package/md_cg/audit.py +652 -379
- package/md_cg/bench6_arms.py +1 -1
- package/md_cg/bench_p0.py +1 -1
- package/md_cg/branches.py +2 -2
- package/md_cg/ccgc.py +1071 -1006
- package/md_cg/chain.py +1 -1
- package/md_cg/consistency.py +97 -9
- package/md_cg/consolidate.py +34 -9
- package/md_cg/crypto.py +59 -26
- package/md_cg/docindex.py +16 -1
- package/md_cg/evolution.py +17 -1
- package/md_cg/export.py +1 -1
- package/md_cg/forgetting.py +309 -13
- package/md_cg/fsutil.py +454 -8
- package/md_cg/identity.py +3 -3
- package/md_cg/insight.py +24 -3
- package/md_cg/linkref.py +1 -1
- package/md_cg/logref.py +327 -0
- package/md_cg/mcp_server.py +4152 -3818
- package/md_cg/mdcg.py +4208 -3547
- package/md_cg/mdcos.py +4491 -4159
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +639 -575
- package/md_cg/protect.py +158 -11
- package/md_cg/protocol.py +41 -2
- package/md_cg/provenance.py +22 -4
- package/md_cg/reach.py +4 -4
- package/md_cg/readcache.py +76 -15
- package/md_cg/reconcile.py +1 -1
- package/md_cg/refindex.py +246 -37
- package/md_cg/refine.py +1 -1
- package/md_cg/routing.py +32 -5
- package/md_cg/run_tests.py +17 -0
- package/md_cg/scrub.py +30 -3
- package/md_cg/security.py +47 -1
- package/md_cg/self_state.py +5 -5
- package/md_cg/sources.py +23 -6
- package/md_cg/srcindex.py +352 -0
- package/md_cg/stg.py +47 -3
- package/md_cg/subgraph.py +2 -2
- package/md_cg/sustain.py +1299 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_b1_auto_id_multiproc.py +277 -0
- package/md_cg/test_b1b2_write_face.py +417 -0
- package/md_cg/test_b2_sensitivity_landing.py +223 -0
- package/md_cg/test_b3_merge_keeps_content.py +576 -0
- package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
- package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
- package/md_cg/test_c3_transient_read_negative.py +793 -0
- package/md_cg/test_c8_search_rrf_gates.py +502 -0
- package/md_cg/test_ccg_form_parity.py +188 -0
- package/md_cg/test_ccgc.py +28 -3
- package/md_cg/test_govern_directread.py +17 -4
- package/md_cg/test_h2_session_view_norm.py +233 -0
- package/md_cg/test_h4_sustain_snapshot.py +1377 -0
- package/md_cg/test_hive_ingest.py +285 -285
- package/md_cg/test_issue39_utf8_stdio.py +307 -16
- package/md_cg/test_issue43_default_policy.py +865 -0
- package/md_cg/test_legacy_p3_node_id_type.py +375 -0
- package/md_cg/test_linkref.py +10 -3
- package/md_cg/test_lock.py +2 -2
- package/md_cg/test_logref.py +1109 -0
- package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
- package/md_cg/test_m3_h9_semantic_guard.py +525 -0
- package/md_cg/test_mr_m2.py +15 -3
- package/md_cg/test_n130_verify_falsified_protect.py +1 -1
- package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
- package/md_cg/test_n176_link_trust.py +221 -0
- package/md_cg/test_n178_units_jobid_gate.py +212 -0
- package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
- package/md_cg/test_n195_writepath_reload.py +283 -0
- package/md_cg/test_n196_stale_gate_skip.py +169 -0
- package/md_cg/test_n197_n208_write_face_gates.py +471 -0
- package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
- package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
- package/md_cg/test_n201_proposal_visibility.py +382 -0
- package/md_cg/test_n202_session_notes_visibility.py +461 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
- package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
- package/md_cg/test_n209_verify_write_face_gates.py +461 -0
- package/md_cg/test_n212_n213_n224_generation_gates.py +513 -0
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
- package/md_cg/test_n225_nonobject_load.py +1110 -0
- package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
- package/md_cg/test_neg_condition_hits.py +333 -0
- package/md_cg/test_neg_tail_honesty.py +663 -0
- package/md_cg/test_none_id_write_guard.py +4 -3
- package/md_cg/test_opt_batch1_md_cg.py +451 -0
- package/md_cg/test_p1.py +5 -5
- package/md_cg/test_p11_consistency.py +161 -12
- package/md_cg/test_p26_refindex.py +2 -2
- package/md_cg/test_p27_docindex.py +777 -774
- package/md_cg/test_p28_refcheck.py +740 -13
- package/md_cg/test_p29_session_ingest_export.py +2 -2
- package/md_cg/test_p2_mcp.py +12 -1
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- package/md_cg/test_p9c_dedup_hints.py +276 -0
- package/md_cg/test_policy_required_ccg.py +426 -0
- package/md_cg/test_protocol.py +22 -0
- package/md_cg/test_rank_parity_score_mode.py +516 -0
- package/md_cg/test_read_face_input_gates.py +363 -0
- package/md_cg/test_read_face_semantics.py +489 -0
- package/md_cg/test_recall_face_guards.py +812 -0
- package/md_cg/test_rejected_credential_forms.py +188 -0
- package/md_cg/test_rejected_redact.py +146 -0
- package/md_cg/test_retr_s1b.py +2 -2
- package/md_cg/test_retr_s5.py +20 -7
- package/md_cg/test_review_conformance.py +21 -3
- package/md_cg/test_security_audit_v21.py +2 -2
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_v21r1_package_version.py +310 -0
- package/md_cg/test_writepipe.py +10 -4
- package/md_cg/tokens.py +225 -95
- package/md_cg/tool_face.py +4 -4
- package/md_cg/trust.py +49 -5
- package/md_cg/units.py +204 -6
- package/md_cg/weights.py +4 -4
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
- package/md_cg/writelimit.py +42 -8
- package/md_cg/writepipe.py +651 -554
- package/package.json +3 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/hooks.ts +229 -16
- package/src/init.ts +15 -1
- package/src/lib/prompt_safety.ts +88 -1
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 可见性/读出口零闸低危机械同批(N204/N205/N226/N227/N228/N229)
|
|
3
|
+
|
|
4
|
+
六项,**修法同范式**:出口接既有单点(`_readable` 经跨层包装
|
|
5
|
+
`security.visible_to`/`node_visible`,或对齐规范出口的 `require_op` 词),
|
|
6
|
+
不新写第二份密级/会话口径。
|
|
7
|
+
|
|
8
|
+
① N205 `md_cg/trust.py:934-971` + `:975-985` × `mcp_server.py:1814/1826-1834`:
|
|
9
|
+
`describe` 在 `cg.get` 被读闸拒后**回落索引条目**仍报 ok=True,并附
|
|
10
|
+
`load_ledger` 全量行(行内 `reason` 含明文证据串)⇒ 任何持 read op 的身份
|
|
11
|
+
(含无令牌 guest)读走读闸拒绝节点的验证态/依赖/履历与裁决证据。
|
|
12
|
+
② N204 `md_cg/docindex.py:186/192-199`:父节摘要先按 MAX_SUMMARY 取满,再并入
|
|
13
|
+
小/超层级子节摘要后整体截断 ⇒ 父节直接正文 ≥200 字时被合并子节的文字
|
|
14
|
+
**一个字都进不去**(合并的唯一目的即「否则这些小节的正文只存在于父节 ref
|
|
15
|
+
区间里,检索不到」)——静默丢索引(非攻击面,可检索性缺陷)。
|
|
16
|
+
③ N226 `md_cg/provenance.py:427`(`_node_digest` 直读 cg.index)+`539-543`:
|
|
17
|
+
`cg(op="edges", expand_nodes=true)` 的端点摘要与边拓扑零可见性闸,两端都
|
|
18
|
+
不可见的边照返回、`aggregates.sample` 亦回 `child->parent`(同库同身份的
|
|
19
|
+
`stg.timeline` stg.py:133/172 早已接线 `_readable`——本处是漏网出口)。
|
|
20
|
+
④ N227 `md_cg/evolution.py:265/332` × `mdcos.py:3261`(health_os 挂
|
|
21
|
+
evolution.summary)× `mcp_server.py:3071-3072/2125/3288-3292`:演化台账
|
|
22
|
+
(`_evolution/ledger.md`)出口无可见性/身份闸,且工具面粗粒度 op 映射
|
|
23
|
+
使 guest 可达;同一份自由文本还被 health_os→`cg(op=info)`/`mdcg_health`
|
|
24
|
+
带出。
|
|
25
|
+
⑤ N228 `mcp_server.py:3063`("mdcg_protect":"write")/`:3264`/`:1155-1179`
|
|
26
|
+
(snapshot 分支)× `protect.py:153-170`:写保护面被映射到**粗粒度 write**
|
|
27
|
+
(protect op 在 cg 面是 designer 专属)⇒ 任何 can_write 角色可
|
|
28
|
+
`action=snapshot` 真落盘 `_protected_history` 且 `protect.snapshot` 全文
|
|
29
|
+
零 `_audit`(越作用域+无审计+污染后续 override 快照链)。
|
|
30
|
+
⑥ N229 `md_cg/forgetting.py:289-306` × `mcp_server.py:3068/3267/1162-1163`:
|
|
31
|
+
`forgetting.history` 逐行 `json.loads(_forgetting.jsonl)` 原文返回
|
|
32
|
+
(actor/verdict/reason/importance/duplicate_with),零可见性、零归属过滤。
|
|
33
|
+
|
|
34
|
+
修复(最小改动,全部复用单点):
|
|
35
|
+
· 新增跨层可见性单点 `security.visible_to(cg, entry)` / `node_visible(cg, node_id)`
|
|
36
|
+
(口径 = `_readable`:无钩子=不限制、有钩子=按判据、判据异常/不可证=不可见;
|
|
37
|
+
`can_admin` 豁免)。`mdcos._note_visible` 改为委派它(同一实现)。
|
|
38
|
+
· ① `trust.describe` 在 get 被拒且节点不可见时返回 `node_not_found`(「不可见
|
|
39
|
+
即不存在」,与 get/search/recall 同口径);`trust.load_ledger` 逐行按行所属
|
|
40
|
+
node_id 过滤。
|
|
41
|
+
· ② `docindex` 摘要改为**预算预分配**:合并段最多占 MAX_SUMMARY 的一半,父段
|
|
42
|
+
按「总预算 − 合并段实占 − 分隔符」取 ⇒ 合并段保证进得去。
|
|
43
|
+
· ③ `provenance._node_digest` 不可见端点返回 `present=False`(不附任何字段);
|
|
44
|
+
`find_edges` 在谓词过滤前先按「两端都可见」过滤边(半条边同样泄露隐藏端点
|
|
45
|
+
id 与关系)。
|
|
46
|
+
· ④ `evolution.entries` 逐行按 node_id 过滤(summary/patterns/history/show/
|
|
47
|
+
health_os 全部经此单点);工具面 `mdcg_evolution` op 由 read 对齐到规范出口
|
|
48
|
+
的 `evolution`。
|
|
49
|
+
· ⑤ `mdcg_protect` op 由 write 对齐到 `protect`(designer 专属);`protect.snapshot`
|
|
50
|
+
成功即 `_audit(action="snapshot")`。
|
|
51
|
+
· ⑥ `forgetting.history` 逐行按 node_id 过滤;`mdcg_forgetting_history` op 由
|
|
52
|
+
read 对齐到规范出口的 `protect`。
|
|
53
|
+
|
|
54
|
+
守卫(八组,全部临时库 + 哑主密钥 + 临时令牌库,**绝不触真实 ~/.mdcg**):
|
|
55
|
+
⓿ 隔离自证 + 夹具;① N205 两出口(含设计者/可见节点阳性对照);
|
|
56
|
+
② N204 纯函数三档父长(440/200/199)+ 上限契约 + 无合并段对照;
|
|
57
|
+
③ N226 边/摘要/聚合 + 可见边阳性对照;④ N227 工具面与库面 + health 面;
|
|
58
|
+
⑤ N228 工具面越权 + 审计留痕;⑥ N229 工具面 + 行级过滤;
|
|
59
|
+
⑦ 同族横扫(guest 只读面全扫,任何私密标记都不许出现);
|
|
60
|
+
⑧ 源断言 + live floor。
|
|
61
|
+
|
|
62
|
+
如实边界(本轮残留,见报告):演化账本/遗忘留痕的**自由文本内跨节点引用**
|
|
63
|
+
不做机械脱敏(只能按行所属 node_id 判定);`trust.load_ledger` 对已删节点
|
|
64
|
+
(索引无条目)在有身份模型时 fail-closed 丢弃(设计者豁免)。
|
|
65
|
+
|
|
66
|
+
运行:python -X utf8 -m md_cg.test_n204_n205_n226_n227_n228_n229_exit_gates
|
|
67
|
+
"""
|
|
68
|
+
from __future__ import annotations
|
|
69
|
+
|
|
70
|
+
import atexit
|
|
71
|
+
import json
|
|
72
|
+
import os
|
|
73
|
+
import shutil
|
|
74
|
+
import sys
|
|
75
|
+
import tempfile
|
|
76
|
+
import time
|
|
77
|
+
|
|
78
|
+
# ── 隔离前置:必须在 import md_cg 子模块**之前**(tokens/theory 等在模块级把
|
|
79
|
+
# aux_root() 冻成常量)——否则会指到真实 ~/.mdcg 并在那里建密钥/令牌库。 ──
|
|
80
|
+
_TMP = tempfile.mkdtemp(prefix="n2xx_")
|
|
81
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
82
|
+
os.environ["MDCG_MASTER_KEY"] = "34" * 32 # 哑主密钥(绝不触真实密钥面)
|
|
83
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP, "tokens.json")
|
|
84
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
|
|
85
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
86
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
87
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_ACTOR", "MDCG_STATE_ROOT",
|
|
88
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES",
|
|
89
|
+
"PYTHONIOENCODING", "PYTHONUTF8"):
|
|
90
|
+
os.environ.pop(_k, None)
|
|
91
|
+
|
|
92
|
+
from . import (crypto, docindex, evolution, forgetting, # noqa: E402
|
|
93
|
+
mcp_server, protect, provenance, security, tokens, trust)
|
|
94
|
+
from .datapath import aux_root # noqa: E402
|
|
95
|
+
from .mdcos import MdCGSecure # noqa: E402
|
|
96
|
+
from .security import Principal # noqa: E402
|
|
97
|
+
|
|
98
|
+
atexit.register(lambda: _cleanup(_TMP)) # LIFO:晚于库层 _atexit_flush_all
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
102
|
+
except AttributeError:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
ROOT = os.path.join(_TMP, "cg")
|
|
106
|
+
PASS = FAIL = LIVE = 0
|
|
107
|
+
LIVE_FLOOR = 26
|
|
108
|
+
FAILS = []
|
|
109
|
+
|
|
110
|
+
S_DESIGN = "sess_n2xx_design"
|
|
111
|
+
EVID = "N2XX-EVID-XYZ-外部证据串"
|
|
112
|
+
EVO_ROW = "N2XX-EVO-RULE-XYZ"
|
|
113
|
+
EVO_VIS = "N2XX-EVO-VIS-OK"
|
|
114
|
+
PRIV_BODY = "# 功能名:私密节点\n# 正文:N2XX-PRIV-BODY(designer 私有)\n"
|
|
115
|
+
VIS_BODY = "# 功能名:共享节点\n# 正文:N2XX-VIS-BODY(internal 跨会话共享)\n"
|
|
116
|
+
# 遗忘闸门专用正文:必须与上面两条**不同**(重复度会判 DROP 而不落节点,
|
|
117
|
+
# 那么「阳性对照」就会因为节点不存在而被 fail-closed 过滤掉,断言形同空转)
|
|
118
|
+
FG_VIS_BODY = "# 功能名:闸门留痕节点\n# 正文:N2XX-FG-VIS-BODY(独立内容,闸门应 ACCEPT)\n"
|
|
119
|
+
|
|
120
|
+
N_PRIV = "n2xx_priv"
|
|
121
|
+
N_VIS = "n2xx_vis"
|
|
122
|
+
N_CHILD_PRIV = "n2xx_child_priv"
|
|
123
|
+
N_CHILD_VIS = "n2xx_child_vis"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def check(name, cond, detail="", live=False):
|
|
127
|
+
global PASS, FAIL, LIVE
|
|
128
|
+
if live:
|
|
129
|
+
LIVE += 1
|
|
130
|
+
if cond:
|
|
131
|
+
PASS += 1
|
|
132
|
+
print(" [PASS] %s" % name)
|
|
133
|
+
else:
|
|
134
|
+
FAIL += 1
|
|
135
|
+
FAILS.append(name)
|
|
136
|
+
print(" [FAIL] %s · %s" % (name, detail))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _princ(role, clearance="internal", can_admin=False, session=None, actor=None):
|
|
140
|
+
spec = tokens.role_spec(role)
|
|
141
|
+
return Principal(tenant="default", actor=actor or ("n2xx-" + role),
|
|
142
|
+
clearance=clearance, can_write=bool(spec["can_write"]),
|
|
143
|
+
can_admin=can_admin, role=role, session=session,
|
|
144
|
+
layers_allow=spec["layers_allow"],
|
|
145
|
+
ops_allow=spec["ops_allow"], auth_mode="test")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_OPEN = []
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _secured(principal):
|
|
152
|
+
cg = MdCGSecure(ROOT, principal=principal)
|
|
153
|
+
_OPEN.append(cg)
|
|
154
|
+
return cg
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _close_all():
|
|
158
|
+
for cg in _OPEN:
|
|
159
|
+
try:
|
|
160
|
+
cg.close()
|
|
161
|
+
except Exception: # noqa: BLE001 —— 清理面不得再抛
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _cleanup(path) -> bool:
|
|
166
|
+
import gc
|
|
167
|
+
for _ in range(6):
|
|
168
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
169
|
+
if not os.path.exists(path):
|
|
170
|
+
return True
|
|
171
|
+
gc.collect()
|
|
172
|
+
time.sleep(0.25)
|
|
173
|
+
return not os.path.exists(path)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _raises(fn, *a, **kw):
|
|
177
|
+
"""执行并返回 (ok, 异常类名/空, 结果/异常文本)。"""
|
|
178
|
+
try:
|
|
179
|
+
return True, "", fn(*a, **kw)
|
|
180
|
+
except Exception as exc: # noqa: BLE001 —— 守卫要的是拒绝形态
|
|
181
|
+
return False, type(exc).__name__, str(exc)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _read_src(rel):
|
|
185
|
+
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), rel)
|
|
186
|
+
with open(p, encoding="utf-8") as f:
|
|
187
|
+
return f.read()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _dump(x):
|
|
191
|
+
try:
|
|
192
|
+
return json.dumps(x, ensure_ascii=False, default=str)
|
|
193
|
+
except Exception: # noqa: BLE001
|
|
194
|
+
return str(x)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _audit_rows(cg, action=None):
|
|
198
|
+
out = []
|
|
199
|
+
p = os.path.join(ROOT, protect.AUDIT_FILE)
|
|
200
|
+
if not os.path.exists(p):
|
|
201
|
+
return out
|
|
202
|
+
with open(p, encoding="utf-8") as f:
|
|
203
|
+
for line in f:
|
|
204
|
+
line = line.strip()
|
|
205
|
+
if not line:
|
|
206
|
+
continue
|
|
207
|
+
try:
|
|
208
|
+
r = json.loads(line)
|
|
209
|
+
except Exception: # noqa: BLE001
|
|
210
|
+
continue
|
|
211
|
+
if action and r.get("action") != action:
|
|
212
|
+
continue
|
|
213
|
+
out.append(r)
|
|
214
|
+
return out
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ---------------------------------------------------------------- N204 夹具
|
|
218
|
+
def _doc(parent_len, child_text, with_child=True):
|
|
219
|
+
"""构造一份 md:父节直接正文 parent_len 字(+ 可选一个小子节)。"""
|
|
220
|
+
lines = ["# 父节标题", ""]
|
|
221
|
+
lines.append("父节直接正文" + ("甲" * max(0, parent_len - 6)))
|
|
222
|
+
if with_child:
|
|
223
|
+
lines += ["", "## 小子节标题", "", child_text]
|
|
224
|
+
return "\n".join(lines) + "\n"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def main():
|
|
228
|
+
global LIVE
|
|
229
|
+
# ---------- ⓿ 隔离自证 + 夹具 ----------
|
|
230
|
+
print("[0] 隔离自证 + 夹具")
|
|
231
|
+
_tok = tokens.token_file()
|
|
232
|
+
check("0a 默认令牌库与 aux 根都落在守卫临时目录(绝不触真实凭据)",
|
|
233
|
+
os.path.abspath(_tok).lower().startswith(os.path.abspath(_TMP).lower())
|
|
234
|
+
and os.path.abspath(aux_root()).lower().startswith(
|
|
235
|
+
os.path.abspath(_TMP).lower()),
|
|
236
|
+
"tok=%s aux=%s" % (_tok, aux_root()))
|
|
237
|
+
if not os.path.abspath(ROOT).lower().startswith(
|
|
238
|
+
os.path.abspath(_TMP).lower()):
|
|
239
|
+
print("FATAL: 隔离失败,中止(绝不带病跑真实库)")
|
|
240
|
+
return 2
|
|
241
|
+
|
|
242
|
+
design = _secured(_princ("designer", clearance="secret", can_admin=True,
|
|
243
|
+
session=S_DESIGN, actor="n2xx-design"))
|
|
244
|
+
# 私密节点(private + 会话绑定)与共享节点(internal 跨会话)
|
|
245
|
+
design.add(N_PRIV, PRIV_BODY, layer="knowledge", sensitivity="private",
|
|
246
|
+
tags=["n2xx", "tag-PRIV-XYZ"], session=S_DESIGN)
|
|
247
|
+
design.add(N_VIS, VIS_BODY, layer="knowledge", tags=["n2xx"])
|
|
248
|
+
# 派生边:① 可见→不可见(N226 病灶)② 可见→可见(阳性对照)
|
|
249
|
+
design.add(N_CHILD_PRIV, "# 功能名:派生边子节点(指向私密父)\n",
|
|
250
|
+
layer="knowledge", tags=["n2xx"], derived_from=N_PRIV)
|
|
251
|
+
design.add(N_CHILD_VIS, "# 功能名:派生边子节点(指向可见父)\n",
|
|
252
|
+
layer="knowledge", tags=["n2xx"], derived_from=N_VIS)
|
|
253
|
+
# 验证态台账:两条(私密节点 / 共享节点)
|
|
254
|
+
design.verify(N_PRIV, EVID, "confirmed")
|
|
255
|
+
design.verify(N_VIS, "N2XX-EVID-VIS-OK", "confirmed")
|
|
256
|
+
design.flush()
|
|
257
|
+
# 演化台账:两条(私密节点 / 共享节点)
|
|
258
|
+
evolution.record(design, N_PRIV, pattern=EVO_ROW, missing="条件空间:未声明",
|
|
259
|
+
action="N2XX-EVO-DETAIL-XYZ")
|
|
260
|
+
evolution.record(design, N_VIS, pattern=EVO_VIS, missing="条件空间:已声明",
|
|
261
|
+
action="N2XX-EVO-VIS-ACT")
|
|
262
|
+
# 遗忘留痕:私密候选(DROP/ACCEPT 皆写行)+ 共享节点留痕
|
|
263
|
+
design.remember_gated("n2xx_fg_priv", PRIV_BODY, layer="knowledge",
|
|
264
|
+
sensitivity="private", session=S_DESIGN,
|
|
265
|
+
verification_basis="test")
|
|
266
|
+
design.remember_gated("n2xx_fg_vis", FG_VIS_BODY, layer="knowledge",
|
|
267
|
+
verification_basis="test")
|
|
268
|
+
design.flush()
|
|
269
|
+
_ev = len(evolution.entries(design, limit=0))
|
|
270
|
+
_fh = len(forgetting.history(design, limit=100))
|
|
271
|
+
check("0b 夹具就绪(私密/共享节点、两条边、台账/演化/遗忘留痕均在位)",
|
|
272
|
+
design.index["nodes"].get(N_PRIV) is not None
|
|
273
|
+
and design.index["nodes"].get(N_VIS) is not None
|
|
274
|
+
and _ev >= 2 and _fh >= 2,
|
|
275
|
+
"evo=%d forget=%d" % (_ev, _fh))
|
|
276
|
+
|
|
277
|
+
guest = _secured(_princ("guest"))
|
|
278
|
+
rec = _secured(_princ("record", session="sess_n2xx_rec"))
|
|
279
|
+
# 阳性对照前置:其它身份确实读不到私密节点本体(既有闸仍然在位)
|
|
280
|
+
check("0c 前置对照:guest/record 对私密节点 get→None(本批不重复修既有闸)",
|
|
281
|
+
guest.get(N_PRIV) is None and rec.get(N_PRIV) is None)
|
|
282
|
+
|
|
283
|
+
# ---------- ① N205 trust.status 两出口 ----------
|
|
284
|
+
print("\n【①】N205 trust.status:describe 与 ledger 的可见性")
|
|
285
|
+
ok_1, _e1, r_1 = _raises(mcp_server._dispatch, guest, "cg",
|
|
286
|
+
{"op": "status", "node_id": N_PRIV})
|
|
287
|
+
check("1a guest cg(op=status,node_id=私密) 不再回落索引条目报 ok=True",
|
|
288
|
+
(not ok_1) or (not (r_1 or {}).get("ok"))
|
|
289
|
+
or (r_1 or {}).get("error") == "node_not_found",
|
|
290
|
+
"ok=%r res=%r" % (ok_1, _dump(r_1)[:160]), live=True)
|
|
291
|
+
ok_2, _e2, r_2 = _raises(mcp_server._dispatch, guest, "cg",
|
|
292
|
+
{"op": "status", "action": "ledger", "limit": 100})
|
|
293
|
+
_led = _dump((r_2 or {}).get("rows") if ok_2 else r_2)
|
|
294
|
+
check("1b guest 验证态台账不再含私密节点的裁决证据串",
|
|
295
|
+
ok_2 and EVID not in _led and N_PRIV not in _led,
|
|
296
|
+
"exc=%r EVID命中=%r priv命中=%r" % (_e2, EVID in _led, N_PRIV in _led),
|
|
297
|
+
live=True)
|
|
298
|
+
ok_3, _e3, r_3 = _raises(mcp_server._dispatch, guest, "cg",
|
|
299
|
+
{"op": "status", "node_id": N_VIS})
|
|
300
|
+
check("1c 阳性对照:可见节点的验证态照常返回(未过度收口)",
|
|
301
|
+
ok_3 and bool((r_3 or {}).get("ok"))
|
|
302
|
+
and (r_3 or {}).get("verification_state") == "verified",
|
|
303
|
+
"res=%r" % _dump(r_3)[:140], live=True)
|
|
304
|
+
ok_4, _e4, r_4 = _raises(mcp_server._dispatch, design, "cg",
|
|
305
|
+
{"op": "status", "node_id": N_PRIV})
|
|
306
|
+
check("1d 设计者(can_admin)仍可取私密节点验证态(治理本职不被误禁)",
|
|
307
|
+
ok_4 and bool((r_4 or {}).get("ok")),
|
|
308
|
+
"res=%r" % _dump(r_4)[:140], live=True)
|
|
309
|
+
_led_d = _dump(trust.load_ledger(design, limit=200))
|
|
310
|
+
check("1e 设计者台账仍含该证据串(未把治理面掏空)",
|
|
311
|
+
EVID in _led_d, "len=%d" % len(trust.load_ledger(design, limit=200)),
|
|
312
|
+
live=True)
|
|
313
|
+
_lib = trust.describe(guest, N_PRIV)
|
|
314
|
+
check("1f 库层 describe 同口径(单点在库不在工具面)",
|
|
315
|
+
not _lib.get("ok") and _lib.get("error") == "node_not_found",
|
|
316
|
+
_dump(_lib)[:140], live=True)
|
|
317
|
+
|
|
318
|
+
# ---------- ② N204 docindex 摘要预算 ----------
|
|
319
|
+
print("\n【②】N204 docindex:合并子节文字必须进得去")
|
|
320
|
+
_uniq = "子节独有文本-ZZZ-仅此一处"
|
|
321
|
+
for _plen, _tag in ((440, "2a"), (200, "2b"), (199, "2c")):
|
|
322
|
+
items = docindex.extract(_doc(_plen, _uniq), path="x.md")
|
|
323
|
+
_hit = any(_uniq in (it.get("summary_parts") or "") for it in items)
|
|
324
|
+
check("%s 父节直接正文 %d 字时,被合并子节的独有文本仍在摘要中"
|
|
325
|
+
% (_tag, _plen), _hit,
|
|
326
|
+
"items=%d summaries=%r" % (len(items),
|
|
327
|
+
[(it.get("name"), len(it.get("summary_parts") or ""))
|
|
328
|
+
for it in items]), live=True)
|
|
329
|
+
items_len = docindex.extract(_doc(440, _uniq), path="x.md")
|
|
330
|
+
check("2d 摘要仍不超 MAX_SUMMARY 上限(契约不变)",
|
|
331
|
+
all(len(it.get("summary_parts") or "") <= docindex.MAX_SUMMARY
|
|
332
|
+
for it in items_len),
|
|
333
|
+
"max=%r" % max(len(it.get("summary_parts") or "") for it in items_len))
|
|
334
|
+
items_ctl = docindex.extract(_doc(300, "", with_child=False), path="x.md")
|
|
335
|
+
check("2e 对照:无合并子节时父节自身文字照旧进摘要(预算改动无副作用)",
|
|
336
|
+
bool(items_ctl) and "父节直接正文" in (items_ctl[0].get("summary_parts") or ""),
|
|
337
|
+
_dump(items_ctl)[:120])
|
|
338
|
+
|
|
339
|
+
# ---------- ③ N226 provenance.find_edges ----------
|
|
340
|
+
print("\n【③】N226 派生边出口的可见性")
|
|
341
|
+
ok_5, _e5, r_5 = _raises(mcp_server._dispatch, guest, "cg",
|
|
342
|
+
{"op": "edges", "expand_nodes": True, "limit": 50})
|
|
343
|
+
_d5 = _dump(r_5 if ok_5 else _e5)
|
|
344
|
+
check("3a guest 边列表不含指向私密节点的边(拓扑不泄露隐藏端点)",
|
|
345
|
+
ok_5 and N_PRIV not in _d5,
|
|
346
|
+
"exc=%r priv命中=%r" % (_e5, N_PRIV in _d5), live=True)
|
|
347
|
+
_digests = []
|
|
348
|
+
for _e in ((r_5 or {}).get("edges") or []) if ok_5 else []:
|
|
349
|
+
for _k in ("child_node", "parent_node"):
|
|
350
|
+
if isinstance(_e.get(_k), dict):
|
|
351
|
+
_digests.append(_e[_k])
|
|
352
|
+
check("3b guest 端点摘要不含私密节点(present=False 而非元数据照回)",
|
|
353
|
+
ok_5 and all(d.get("id") != N_PRIV for d in _digests),
|
|
354
|
+
"digests=%r" % [d.get("id") for d in _digests], live=True)
|
|
355
|
+
check("3c 阳性对照:两端都可见的派生边照常返回",
|
|
356
|
+
ok_5 and N_CHILD_VIS in _d5 and N_VIS in _d5,
|
|
357
|
+
"exc=%r" % _e5, live=True)
|
|
358
|
+
ok_6, _e6, r_6 = _raises(mcp_server._dispatch, design, "cg",
|
|
359
|
+
{"op": "edges", "expand_nodes": True, "limit": 50})
|
|
360
|
+
check("3d 设计者仍看得到指向私密节点的边与摘要(全局观测本职)",
|
|
361
|
+
ok_6 and N_PRIV in _dump(r_6), "exc=%r" % _e6, live=True)
|
|
362
|
+
ok_7, _e7, r_7 = _raises(mcp_server._dispatch, guest, "cg",
|
|
363
|
+
{"op": "edges", "aggregation": "by_child",
|
|
364
|
+
"limit": 50})
|
|
365
|
+
check("3e guest 聚合 sample 不再出现 child->priv 关系串",
|
|
366
|
+
ok_7 and N_PRIV not in _dump(r_7),
|
|
367
|
+
"exc=%r sample=%r" % (_e7, _dump((r_7 or {}).get("aggregates"))[:160]),
|
|
368
|
+
live=True)
|
|
369
|
+
|
|
370
|
+
# ---------- ④ N227 evolution 台账 ----------
|
|
371
|
+
print("\n【④】N227 演化台账出口")
|
|
372
|
+
ok_8, e_8, _r8 = _raises(mcp_server._dispatch, guest, "mdcg_evolution",
|
|
373
|
+
{"action": "entries", "limit": 50})
|
|
374
|
+
check("4a guest 经工具面 mdcg_evolution 被 AccessDenied(与 cg op 同口径)",
|
|
375
|
+
(not ok_8) and e_8 == "AccessDenied", "exc=%r" % e_8, live=True)
|
|
376
|
+
_ge = evolution.entries(guest, limit=0)
|
|
377
|
+
check("4b 库层 entries 逐行按 node_id 过滤(私密台账行整条不出)",
|
|
378
|
+
all(r.get("node_id") != N_PRIV for r in _ge)
|
|
379
|
+
and all(EVO_ROW not in _dump(r) for r in _ge),
|
|
380
|
+
"rows=%r" % [_dump(r)[:60] for r in _ge][:4], live=True)
|
|
381
|
+
check("4c 阳性对照:可见节点的演化条目照常返回",
|
|
382
|
+
any(r.get("node_id") == N_VIS and EVO_VIS in _dump(r) for r in _ge),
|
|
383
|
+
"rows=%d" % len(_ge), live=True)
|
|
384
|
+
ok_9, _e9, r_9 = _raises(mcp_server._dispatch, guest, "mdcg_health", {})
|
|
385
|
+
_hr = _dump(((r_9 or {}).get("os") or {}).get("evolution", {}).get("recent")
|
|
386
|
+
if ok_9 else r_9)
|
|
387
|
+
check("4d health 面(op=read 可达)带出的演化摘要不含私密台账行",
|
|
388
|
+
ok_9 and N_PRIV not in _hr and EVO_ROW not in _hr,
|
|
389
|
+
"exc=%r recent=%r" % (_e9, _hr[:160]), live=True)
|
|
390
|
+
ok_10, _e10, r_10 = _raises(mcp_server._dispatch, design, "cg", {"op": "info"})
|
|
391
|
+
_hrd = _dump(((r_10 or {}).get("os") or {}).get("evolution", {}).get("recent")
|
|
392
|
+
if ok_10 else r_10)
|
|
393
|
+
check("4e 设计者 info 面仍带私密台账行(治理面不掏空)",
|
|
394
|
+
ok_10 and EVO_ROW in _hrd, "exc=%r" % _e10, live=True)
|
|
395
|
+
|
|
396
|
+
# ---------- ⑤ N228 写保护面 ----------
|
|
397
|
+
print("\n【⑤】N228 写保护面:作用域 + 审计")
|
|
398
|
+
_before = len(_audit_rows(design, "snapshot"))
|
|
399
|
+
ok_11, e_11, _r11 = _raises(mcp_server._dispatch, rec, "mdcg_protect",
|
|
400
|
+
{"action": "snapshot", "node_id": N_VIS})
|
|
401
|
+
check("5a record(can_write)经工具面 snapshot 被 AccessDenied(越作用域已闭)",
|
|
402
|
+
(not ok_11) and e_11 == "AccessDenied", "exc=%r" % e_11, live=True)
|
|
403
|
+
_snap = protect.snapshot(design, N_VIS)
|
|
404
|
+
check("5b protect.snapshot 落盘即留痕(新增 action=snapshot 审计行)",
|
|
405
|
+
bool(_snap) and len(_audit_rows(design, "snapshot")) == _before + 1,
|
|
406
|
+
"snap=%r rows=%d→%d" % (_snap, _before,
|
|
407
|
+
len(_audit_rows(design, "snapshot"))), live=True)
|
|
408
|
+
ok_12, e_12, r_12 = _raises(mcp_server._dispatch, design, "mdcg_protect",
|
|
409
|
+
{"action": "stats"})
|
|
410
|
+
check("5c 设计者 protect stats 照常可用(未过度收口)",
|
|
411
|
+
ok_12 and (r_12 or {}).get("protected_count", -1) >= 0,
|
|
412
|
+
"exc=%r res=%r" % (e_12, _dump(r_12)[:120]), live=True)
|
|
413
|
+
ok_13, e_13, _r13 = _raises(mcp_server._dispatch, rec, "mdcg_protect",
|
|
414
|
+
{"action": "stats"})
|
|
415
|
+
check("5d record 经工具面 protect stats 亦被 AccessDenied(盘点面不越权)",
|
|
416
|
+
(not ok_13) and e_13 == "AccessDenied", "exc=%r" % e_13, live=True)
|
|
417
|
+
|
|
418
|
+
# ---------- ⑥ N229 遗忘留痕 ----------
|
|
419
|
+
print("\n【⑥】N229 遗忘留痕出口")
|
|
420
|
+
ok_14, e_14, _r14 = _raises(mcp_server._dispatch, guest,
|
|
421
|
+
"mdcg_forgetting_history", {"limit": 100})
|
|
422
|
+
check("6a guest 经工具面 mdcg_forgetting_history 被 AccessDenied",
|
|
423
|
+
(not ok_14) and e_14 == "AccessDenied", "exc=%r" % e_14, live=True)
|
|
424
|
+
_gh = forgetting.history(guest, limit=100)
|
|
425
|
+
check("6b 库层 history 逐行按 node_id 过滤(私密候选行整条不出)",
|
|
426
|
+
all(r.get("node_id") != "n2xx_fg_priv" for r in _gh)
|
|
427
|
+
and PRIV_BODY not in _dump(_gh),
|
|
428
|
+
"rows=%r" % [_dump(r)[:70] for r in _gh][:4], live=True)
|
|
429
|
+
check("6c 阳性对照:共享节点的遗忘留痕照常返回",
|
|
430
|
+
any(r.get("node_id") == "n2xx_fg_vis" for r in _gh),
|
|
431
|
+
"rows=%d" % len(_gh), live=True)
|
|
432
|
+
ok_15, e_15, _r15 = _raises(mcp_server._dispatch, rec,
|
|
433
|
+
"mdcg_forgetting_history", {"limit": 100})
|
|
434
|
+
check("6d record 经工具面亦被 AccessDenied(他人写入裁决不跨身份外露)",
|
|
435
|
+
(not ok_15) and e_15 == "AccessDenied", "exc=%r" % e_15, live=True)
|
|
436
|
+
|
|
437
|
+
# ---------- ⑦ 同族横扫(guest 只读面全扫) ----------
|
|
438
|
+
print("\n【⑦】同族横扫:guest 只读面不许出现任何私密标记")
|
|
439
|
+
# 标记集:**必须绝不出现**的私密面内容(正文标记/证据串/演化行/私密标签)。
|
|
440
|
+
# 私密节点 **id 本身**单独判(7c):可见节点自身的 `derived_from` 声明里
|
|
441
|
+
# 本来就会写出它的父 id(那是该可见节点自己的溯源字段,属另一面,本批
|
|
442
|
+
# 不redact——见报告残留下结论)。
|
|
443
|
+
_marks = (EVID, EVO_ROW, "N2XX-PRIV-BODY", "tag-PRIV-XYZ")
|
|
444
|
+
_sweep = [
|
|
445
|
+
("cg", {"op": "status", "action": "ledger", "limit": 200}),
|
|
446
|
+
("cg", {"op": "status"}),
|
|
447
|
+
("cg", {"op": "edges", "expand_nodes": True, "limit": 200}),
|
|
448
|
+
("cg", {"op": "edges", "aggregation": "by_child", "limit": 200}),
|
|
449
|
+
("cg", {"op": "info"}),
|
|
450
|
+
("cg", {"op": "health"}),
|
|
451
|
+
("cg", {"op": "evolution", "action": "entries", "limit": 200}),
|
|
452
|
+
("cg", {"op": "protect", "action": "forgetting"}),
|
|
453
|
+
("mdcg_health", {}),
|
|
454
|
+
("mdcg_evolution", {"action": "entries", "limit": 200}),
|
|
455
|
+
("mdcg_protect", {"action": "stats"}),
|
|
456
|
+
("mdcg_forgetting_history", {"limit": 200}),
|
|
457
|
+
("mdcg_metacognition", {}),
|
|
458
|
+
("mdcg_self_state", {}),
|
|
459
|
+
("mdcg_identity", {}),
|
|
460
|
+
("mdcg_predict", {}),
|
|
461
|
+
("mdcg_causal", {}),
|
|
462
|
+
("mdcg_watermarks", {}),
|
|
463
|
+
("mdcg_whoami", {}),
|
|
464
|
+
("mdcg_service_info", {}),
|
|
465
|
+
("cg", {"op": "search", "query": "N2XX-PRIV-BODY"}),
|
|
466
|
+
("cg", {"op": "recall", "query": "N2XX-PRIV-BODY"}),
|
|
467
|
+
("mdcg_search", {"query": "N2XX-PRIV-BODY"}),
|
|
468
|
+
("mdcg_recall", {"query": "N2XX-PRIV-BODY"}),
|
|
469
|
+
]
|
|
470
|
+
_hits, _okn, _id_dumps = [], 0, []
|
|
471
|
+
_samples = {}
|
|
472
|
+
for _name, _args in _sweep:
|
|
473
|
+
_o, _ex, _res = _raises(mcp_server._dispatch, guest, _name, _args)
|
|
474
|
+
if _o:
|
|
475
|
+
_okn += 1
|
|
476
|
+
_s = _dump(_res)
|
|
477
|
+
for _m in _marks:
|
|
478
|
+
if _m in _s:
|
|
479
|
+
_hits.append("%s%r:%s" % (_name, _args, _m))
|
|
480
|
+
_samples.setdefault("%s:%s" % (_name, _m), _s[:300])
|
|
481
|
+
if N_PRIV in _s:
|
|
482
|
+
_id_dumps.append((_name, _args, _s))
|
|
483
|
+
check("7a guest 只读面全扫:私密正文/证据串/演化行/私密标签均不出现",
|
|
484
|
+
not _hits, "命中=%r 样本=%r" % (_hits[:6], _samples), live=True)
|
|
485
|
+
check("7b 横扫非空转(正常返回≥6 个出口,否则该断言无意义)",
|
|
486
|
+
_okn >= 6, "成功返回出口数=%d/%d" % (_okn, len(_sweep)), live=True)
|
|
487
|
+
# 7c:私密 id 若出现,只允许是「可见节点自身的 derived_from 声明」这一形态
|
|
488
|
+
# (其余通道由 1b/3a/3b/3e/4b/4d/6b 分别钉死)
|
|
489
|
+
check("7c 私密 id 的出现只限可见节点自身的 derived_from 声明(无第二通道)",
|
|
490
|
+
all("derived_from" in _s for _n, _a, _s in _id_dumps),
|
|
491
|
+
"含 id 出口=%r" % [(n, a) for n, a, _ in _id_dumps][:5], live=True)
|
|
492
|
+
|
|
493
|
+
# ---------- ⑧ 源断言 + floor ----------
|
|
494
|
+
print("\n【⑧】源断言(单点与接线在位)")
|
|
495
|
+
_sec = _read_src("security.py")
|
|
496
|
+
check("8a security 提供跨层可见性单点 visible_to/node_visible",
|
|
497
|
+
"def visible_to(cg, entry)" in _sec and "def node_visible(cg, node_id)" in _sec)
|
|
498
|
+
_os_ = _read_src("mdcos.py")
|
|
499
|
+
check("8b mdcos._note_visible 委派单点(不另立第二份口径)",
|
|
500
|
+
"return _security.visible_to(cg, e)" in _os_)
|
|
501
|
+
_tr = _read_src("trust.py")
|
|
502
|
+
check("8c trust.describe/load_ledger 接单点",
|
|
503
|
+
"_security.node_visible(cg, node_id)" in _tr
|
|
504
|
+
and "_security.node_visible(cg, r.get(\"node_id\"))" in _tr)
|
|
505
|
+
_pv = _read_src("provenance.py")
|
|
506
|
+
check("8d provenance._node_digest/find_edges 接单点",
|
|
507
|
+
_pv.count("_security.node_visible(cg, nid)") >= 1
|
|
508
|
+
and "_security.node_visible(cg, e.get(\"child\"))" in _pv
|
|
509
|
+
and "_security.node_visible(cg, e.get(\"parent\"))" in _pv)
|
|
510
|
+
check("8e evolution.entries 接单点",
|
|
511
|
+
"_security.node_visible(cg, r.get(\"node_id\"))" in _read_src("evolution.py"))
|
|
512
|
+
check("8f forgetting.history 接单点",
|
|
513
|
+
"_security.node_visible(cg, (r or {}).get(\"node_id\"))"
|
|
514
|
+
in _read_src("forgetting.py"))
|
|
515
|
+
_pr = _read_src("protect.py")
|
|
516
|
+
check("8g protect.snapshot 留痕(_audit action=\"snapshot\")",
|
|
517
|
+
'_audit(cg, "snapshot"' in _pr)
|
|
518
|
+
_mcp = _read_src("mcp_server.py")
|
|
519
|
+
check("8h 工具面 op 对齐:protect/evolution/forgetting_history",
|
|
520
|
+
'"mdcg_protect": "protect"' in _mcp
|
|
521
|
+
and '"mdcg_evolution": "evolution"' in _mcp
|
|
522
|
+
and '"mdcg_forgetting_history": "protect"' in _mcp)
|
|
523
|
+
check("8i docindex 摘要预算预分配(merged_budget)",
|
|
524
|
+
"merged_budget" in _read_src("docindex.py"))
|
|
525
|
+
print(" [INFO] live 断言数 = %d(floor=%d)" % (LIVE, LIVE_FLOOR))
|
|
526
|
+
check("8j live 断言数不低于 floor(防空转绿)", LIVE >= LIVE_FLOOR,
|
|
527
|
+
"live=%d" % LIVE)
|
|
528
|
+
|
|
529
|
+
print("\n" + "=" * 64)
|
|
530
|
+
print("PASS=%d FAIL=%d LIVE=%d" % (PASS, FAIL, LIVE))
|
|
531
|
+
if FAILS:
|
|
532
|
+
print("失败项:" + ";".join(FAILS))
|
|
533
|
+
return 0 if FAIL == 0 else 1
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
if __name__ == "__main__":
|
|
537
|
+
try:
|
|
538
|
+
rc = main()
|
|
539
|
+
finally:
|
|
540
|
+
_close_all()
|
|
541
|
+
time.sleep(0.05)
|
|
542
|
+
sys.exit(rc)
|