@furongjun1999/dsh-memory 0.5.1 → 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 -552
- 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/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
- package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
- 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/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -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/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -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_v18.md +183 -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_v19.md +207 -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/dsh/cordis-patch-profile-web.example.yml +35 -0
- package/lib/bridge.js +24 -2
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +66 -0
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.d.ts +88 -0
- package/lib/init.js +302 -0
- 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/evidence.py +585 -582
- 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 -3462
- package/md_cg/mdcos.py +4491 -4137
- 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/review_cli.py +49 -4
- 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_index_crossprocess_reload.py +301 -0
- 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 +166 -0
- 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_cli_attribution.py +177 -0
- package/md_cg/test_review_cli_visibility.py +235 -0
- 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 +8 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/cli.ts +65 -0
- package/src/hooks.ts +229 -16
- package/src/init.ts +361 -0
- package/src/lib/prompt_safety.ts +88 -1
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 索引代际签名 / 陈旧条目可见性 / 重载派生失效(N212 + N213 + N224)
|
|
3
|
+
|
|
4
|
+
三处缺陷同一根:**索引代际口径只在「他进程 compact 落快照」这一种变化上成立**。
|
|
5
|
+
|
|
6
|
+
① **N212 代际签名口径(`md_cg/mdcg.py:1023-1035 / 1053-1055 / 1180-1199`)**
|
|
7
|
+
`_index_signature` 只 stat `_index.json` 的 (mtime_ns, size),而 `flush()`
|
|
8
|
+
**只 append `_index_log` 分片、从不重写快照**。于是「长期存活、autoflush=1
|
|
9
|
+
只 flush 不 close」的写方(MCP serve 本身即此形态)**对本进程永久隐形**:
|
|
10
|
+
· 写保护闸静默失效(N195 修复面回退):`add` 覆写他进程刚写的 anchor 节点
|
|
11
|
+
不抛 ProtectionError、不落 `_protected_history` 快照、不写
|
|
12
|
+
`_protected_audit.jsonl`,旧正文不可恢复且无痕;
|
|
13
|
+
· 存在性假阴性:`get` → None、`forget` → not_found、`override=True` 无效;
|
|
14
|
+
· 文档承诺的逃逸口 `maintain action=reload` 返回 `reloaded=false`。
|
|
15
|
+
次生(②):`prev_entry` 取本进程索引 → 为 None ⇒ `mdcg.py:1794-1812` 的
|
|
16
|
+
「写新文件再删旧文件」整段跳过 ⇒ 同一 id 落成**两份文件**(同层不同桶时),
|
|
17
|
+
重开时索引在两版正文间漂移、一份永久孤儿。
|
|
18
|
+
|
|
19
|
+
③ **N213 陈旧条目判可见性(`md_cg/mdcos.py:4104-4118`)**
|
|
20
|
+
`MdCGSecure.get` 用**本进程陈旧 entry** 的 sensitivity/session 判可见性,而正文
|
|
21
|
+
解密自**当前盘上文件**(文件是真值、判据是旧快照)⇒ 同 actor 异 session 的
|
|
22
|
+
private 节点在被判定「internal 且属本会话」后照常放行,正文以明文返回
|
|
23
|
+
(AEAD 的 AAD 只绑 node_id/tenant/actor,session 是唯一隔离维度)。
|
|
24
|
+
N196 的修复只在 `e is None` 分支探活,命中陈旧条目的路径全程零探活。
|
|
25
|
+
|
|
26
|
+
④ **N213 同根次生(`md_cg/protect.py:63-94`)**
|
|
27
|
+
`_node_entry`/`_stage` **恒落** protected/immutable/self_state 三键(值可为
|
|
28
|
+
None)⇒ `need_fallback` 的三个析取子句恒假、第四个(self_state 缺键且层受保护)
|
|
29
|
+
在本仓所有构造点不可达 ⇒ `_fm` 100% 信任索引、**从不重读文件**。他进程在陈旧
|
|
30
|
+
条目上刚盖的 immutable/protected(`add(immutable=True)` 一类)对
|
|
31
|
+
guard_write / guard_forget / guard_move 全部不可见 → 受保护节点被静默覆写。
|
|
32
|
+
与 `protect.mark`(只改文件 + 内存条目,**不进写日志**)同源。
|
|
33
|
+
|
|
34
|
+
⑤ **N224 重载后派生缓存不失效(`md_cg/mdcg.py:1068-1078`)**
|
|
35
|
+
`_maybe_reload_index` 只清 hotcache/readcache/realpath,**不清** chain/subgraph/
|
|
36
|
+
trust 三个派生缓存(本地写路径 `mdcg.py:1875-1877` 三个都失效)⇒ 重载后邻接表
|
|
37
|
+
仍含「已不可见节点」的 id 与跳转条件(walk/explain/causal_path/
|
|
38
|
+
expand_from_seeds 绕读隔离),新快照的节点/边在下次本地写前**永不进拓扑**。
|
|
39
|
+
|
|
40
|
+
守卫(六组,全部临时库 + 哑主密钥 + 临时令牌库,**绝不触真实 ~/.mdcg**):
|
|
41
|
+
⓿ 隔离自证;① N212 核心(存活写方只 flush:探活/get/保护闸/逃逸口/无痕复刻);
|
|
42
|
+
② N212 次生(同 id 双桶双文件 / 索引指向孤儿);③ N213 跨会话明文泄漏 + 候选面;
|
|
43
|
+
④ N213 同根(immutable 与 protect.mark 的保护位对守卫不可见);
|
|
44
|
+
⑤ N224(不可见化未传播 + 新节点不进 chain/trust/subgraph 缓存);⑥ 源断言 + floor。
|
|
45
|
+
|
|
46
|
+
运行:python -X utf8 -m md_cg.test_n212_n213_n224_generation_gates
|
|
47
|
+
"""
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import atexit
|
|
51
|
+
import json
|
|
52
|
+
import os
|
|
53
|
+
import shutil
|
|
54
|
+
import sys
|
|
55
|
+
import tempfile
|
|
56
|
+
import time
|
|
57
|
+
|
|
58
|
+
# ── 隔离前置:必须在 import md_cg 子模块**之前**(tokens/theory 等在模块级把
|
|
59
|
+
# aux_root() 冻成常量)——否则会指到真实 ~/.mdcg 并在那里建密钥/令牌库。 ──
|
|
60
|
+
_TMP = tempfile.mkdtemp(prefix="n212_")
|
|
61
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
62
|
+
os.environ["MDCG_MASTER_KEY"] = "ef" * 32 # 哑主密钥(绝不触真实密钥面)
|
|
63
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP, "tokens.json")
|
|
64
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
|
|
65
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
66
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
67
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_STATE_ROOT",
|
|
68
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES",
|
|
69
|
+
"PYTHONIOENCODING", "PYTHONUTF8"):
|
|
70
|
+
os.environ.pop(_k, None)
|
|
71
|
+
|
|
72
|
+
from . import chain, crypto, protect, subgraph, tokens, trust # noqa: E402
|
|
73
|
+
from .datapath import aux_root # noqa: E402
|
|
74
|
+
from .mdcos import MdCGSecure # noqa: E402
|
|
75
|
+
from .security import Principal # noqa: E402
|
|
76
|
+
|
|
77
|
+
atexit.register(lambda: _cleanup(_TMP)) # LIFO:晚于库层 _atexit_flush_all
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
81
|
+
except AttributeError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
PASS = FAIL = LIVE = 0
|
|
85
|
+
LIVE_FLOOR = 20
|
|
86
|
+
FAILS = []
|
|
87
|
+
|
|
88
|
+
R1 = os.path.join(_TMP, "cg1") # N212 核心
|
|
89
|
+
R2 = os.path.join(_TMP, "cg2") # N212 次生
|
|
90
|
+
R3 = os.path.join(_TMP, "cg3") # N213 跨会话
|
|
91
|
+
R4 = os.path.join(_TMP, "cg4") # N213 同根(protect 面)
|
|
92
|
+
R5 = os.path.join(_TMP, "cg5") # N224 派生缓存
|
|
93
|
+
R6 = os.path.join(_TMP, "cg6") # N213 第三序面(_dirty 遮蔽 + 正文水合)
|
|
94
|
+
|
|
95
|
+
ANCHOR_BODY = "# 身份锚点(甲):灵枢的锚点声明。\n"
|
|
96
|
+
FORGED = "# 身份锚点(伪):被覆写的伪造内容。\n"
|
|
97
|
+
DUP_B = "# 知识条目(乙):本地磁盘观测路径。\n"
|
|
98
|
+
DUP_A = "# 知识条目(丙):云端观测路径。\n"
|
|
99
|
+
INTERNAL_BODY = "# 情境记录(丁):跨会话泄漏探针·初版(internal)。\n"
|
|
100
|
+
PRIVATE_BODY = "# 情境记录(戊):跨会话泄漏探针·私密档(private)。\n"
|
|
101
|
+
IMM_BODY = "# 情境记录(己):不可覆盖标记探针。\n"
|
|
102
|
+
MARK_BODY = "# 情境记录(庚):显式保护标记探针。\n"
|
|
103
|
+
X_BODY = "# 知识条目(辛):关系链可见性探针。\n"
|
|
104
|
+
PRIV_X = "# 知识条目(壬):被私密化的关系链探针。\n"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def check(name, cond, detail="", live=False):
|
|
108
|
+
global PASS, FAIL, LIVE
|
|
109
|
+
if live:
|
|
110
|
+
LIVE += 1
|
|
111
|
+
if cond:
|
|
112
|
+
PASS += 1
|
|
113
|
+
print(" [PASS] %s" % name)
|
|
114
|
+
else:
|
|
115
|
+
FAIL += 1
|
|
116
|
+
FAILS.append(name)
|
|
117
|
+
print(" [FAIL] %s · %s" % (name, detail))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _princ(role, clearance="internal", can_admin=False, session=None,
|
|
121
|
+
actor=None, layers=None):
|
|
122
|
+
spec = tokens.role_spec(role)
|
|
123
|
+
return Principal(tenant="default", actor=actor or ("n212-" + role),
|
|
124
|
+
clearance=clearance, can_write=bool(spec["can_write"]),
|
|
125
|
+
can_admin=can_admin, role=role, session=session,
|
|
126
|
+
layers_allow=(spec["layers_allow"] if layers is None
|
|
127
|
+
else layers),
|
|
128
|
+
ops_allow=spec["ops_allow"], auth_mode="test")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
_OPEN = []
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _secured(root, principal):
|
|
135
|
+
cg = MdCGSecure(root, principal=principal)
|
|
136
|
+
_OPEN.append(cg)
|
|
137
|
+
return cg
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _designer(root, actor="n212"):
|
|
141
|
+
return _secured(root, _princ("designer", clearance="secret",
|
|
142
|
+
can_admin=True, actor=actor))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _reflect(root, session, actor="n213"):
|
|
146
|
+
"""同 actor 异 session 的非管理身份(reflect:contextual 可写、无管理位)。"""
|
|
147
|
+
return _secured(root, _princ("reflect", clearance="private",
|
|
148
|
+
can_admin=False, session=session, actor=actor,
|
|
149
|
+
layers=("contextual",)))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _close_all():
|
|
153
|
+
for cg in _OPEN:
|
|
154
|
+
try:
|
|
155
|
+
cg.close()
|
|
156
|
+
except Exception: # noqa: BLE001 —— 清理面不得再抛
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _cleanup(path) -> bool:
|
|
161
|
+
import gc
|
|
162
|
+
for _ in range(6):
|
|
163
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
164
|
+
if not os.path.exists(path):
|
|
165
|
+
return True
|
|
166
|
+
gc.collect()
|
|
167
|
+
time.sleep(0.25)
|
|
168
|
+
return not os.path.exists(path)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _raises(fn, *a, **kw):
|
|
172
|
+
"""执行并返回 (ok, 异常类名/空, 结果/异常文本)。"""
|
|
173
|
+
try:
|
|
174
|
+
return True, "", fn(*a, **kw)
|
|
175
|
+
except Exception as exc: # noqa: BLE001 —— 守卫要的是拒绝形态
|
|
176
|
+
return False, type(exc).__name__, str(exc)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _files_named(root, nid):
|
|
180
|
+
"""root 下(含各层/桶目录)名字恰为 <nid>.md 的文件相对路径列表。"""
|
|
181
|
+
out = []
|
|
182
|
+
for dp, _d, files in os.walk(root):
|
|
183
|
+
for fn in files:
|
|
184
|
+
if fn == nid + ".md":
|
|
185
|
+
out.append(os.path.relpath(os.path.join(dp, fn), root)
|
|
186
|
+
.replace("\\", "/"))
|
|
187
|
+
out.sort()
|
|
188
|
+
return out
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _disk_fm(root, nid, actor="n213"):
|
|
192
|
+
"""盘上真值:同 root 新建 designer 实例(自带全量重扫)读 fm 与原始正文。"""
|
|
193
|
+
cg = _designer(root, actor=actor)
|
|
194
|
+
e = (cg.index.get("nodes") or {}).get(nid) or {}
|
|
195
|
+
fm, content = cg._read(e)
|
|
196
|
+
return (fm or {}), (content or ""), e
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _audit_rows(root):
|
|
200
|
+
p = os.path.join(root, protect.AUDIT_FILE)
|
|
201
|
+
if not os.path.exists(p):
|
|
202
|
+
return []
|
|
203
|
+
out = []
|
|
204
|
+
try:
|
|
205
|
+
with open(p, "r", encoding="utf-8") as f:
|
|
206
|
+
for line in f:
|
|
207
|
+
line = line.strip()
|
|
208
|
+
if line:
|
|
209
|
+
try:
|
|
210
|
+
out.append(json.loads(line))
|
|
211
|
+
except ValueError:
|
|
212
|
+
continue
|
|
213
|
+
except OSError:
|
|
214
|
+
return []
|
|
215
|
+
return out
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _src_window(src, start, end):
|
|
219
|
+
i = src.find(start)
|
|
220
|
+
if i < 0:
|
|
221
|
+
return ""
|
|
222
|
+
j = src.find(end, i)
|
|
223
|
+
return src[i:] if j < 0 else src[i:j]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _read_src(rel):
|
|
227
|
+
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), rel)
|
|
228
|
+
with open(p, encoding="utf-8") as f:
|
|
229
|
+
return f.read()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def main():
|
|
233
|
+
global LIVE
|
|
234
|
+
# ---------- ⓿ 隔离自证 ----------
|
|
235
|
+
print("[0] 隔离自证")
|
|
236
|
+
_tok = tokens.token_file()
|
|
237
|
+
check("0a 默认令牌库与 aux 根都落在守卫临时目录(绝不触真实凭据)",
|
|
238
|
+
os.path.abspath(_tok).lower().startswith(os.path.abspath(_TMP).lower())
|
|
239
|
+
and os.path.abspath(aux_root()).lower().startswith(
|
|
240
|
+
os.path.abspath(_TMP).lower()),
|
|
241
|
+
"tok=%s aux=%s" % (_tok, aux_root()))
|
|
242
|
+
for _r in (R1, R2, R3, R4, R5):
|
|
243
|
+
if not os.path.abspath(_r).lower().startswith(
|
|
244
|
+
os.path.abspath(_TMP).lower()):
|
|
245
|
+
print("FATAL: 隔离失败,中止(绝不带病跑真实库)")
|
|
246
|
+
return 2
|
|
247
|
+
|
|
248
|
+
# ================= ① N212 核心:存活写方只 flush =================
|
|
249
|
+
print("\n【①】N212 代际签名:常驻 A + 存活写方 B(只 flush 不 close)")
|
|
250
|
+
a1 = _designer(R1) # 常驻 A(先建:签名基线在 B 写入前)
|
|
251
|
+
a1.add("n212_seed", "# 知识条目(甲):常驻进程初始记忆。\n",
|
|
252
|
+
layer="knowledge")
|
|
253
|
+
a1.flush()
|
|
254
|
+
b1 = _designer(R1, actor="n212b") # 存活写方 B:绝不 close
|
|
255
|
+
b1.add("n212_anchor", ANCHOR_BODY, layer="anchor")
|
|
256
|
+
b1.flush() # 只落分片日志,不写快照
|
|
257
|
+
check("1 setup:盘上已有 B 写入的 anchor 节点(B 存活、快照未重写)",
|
|
258
|
+
_files_named(R1, "n212_anchor") and not os.path.exists(
|
|
259
|
+
os.path.join(R1, "_index.json")),
|
|
260
|
+
"files=%r" % _files_named(R1, "n212_anchor"))
|
|
261
|
+
check("1a A._maybe_reload_index() 为他进程分片日志变化返回 True",
|
|
262
|
+
a1._maybe_reload_index() is True, "reloaded=False(签名只看快照)",
|
|
263
|
+
live=True)
|
|
264
|
+
check("1b A.get 可见他进程刚写入的 anchor 节点(存在性假阴性已消)",
|
|
265
|
+
a1.get("n212_anchor") is not None, "get=None", live=True)
|
|
266
|
+
_imm, _why = protect.is_immutable(a1, "n212_anchor")
|
|
267
|
+
check("1c 保护面判据可见他进程 anchor 节点(is_immutable=True)",
|
|
268
|
+
_imm is True, "is_immutable=%r/%r" % (_imm, _why), live=True)
|
|
269
|
+
b1.add("n212_esc", "# 知识条目(乙):逃逸口探针。\n", layer="knowledge")
|
|
270
|
+
b1.flush() # 新的海外变化(逃逸口用)
|
|
271
|
+
_esc = a1.maintain(action="reload")
|
|
272
|
+
check("1d 逃逸口 maintain reload 对存活写方的写入返回 reloaded=True",
|
|
273
|
+
isinstance(_esc, dict) and _esc.get("reloaded") is True,
|
|
274
|
+
str(_esc)[:120], live=True)
|
|
275
|
+
# 销毁腿:覆写他进程 anchor 节点必须被拒 + 零留痕
|
|
276
|
+
b1.add("n212_ovr", ANCHOR_BODY, layer="anchor")
|
|
277
|
+
b1.flush()
|
|
278
|
+
ok, exc, _res = _raises(a1.add, "n212_ovr", FORGED, layer="anchor")
|
|
279
|
+
check("1e A.add 覆写他进程 anchor 节点被 ProtectionError 拒绝",
|
|
280
|
+
(not ok) and exc == "ProtectionError", "exc=%s" % exc, live=True)
|
|
281
|
+
_fm, _c, _e = _disk_fm(R1, "n212_ovr", actor="n212")
|
|
282
|
+
check("1f 拒绝后盘上正文保持 B 的原文(未被伪造内容覆写)",
|
|
283
|
+
ANCHOR_BODY in _c and FORGED not in _c, repr(_c)[:90], live=True)
|
|
284
|
+
check("1g 拒绝路径零留痕改动:无该 id 的快照目录、无 override_write 审计",
|
|
285
|
+
not os.path.isdir(os.path.join(R1, protect.HISTORY_DIR, "n212_ovr"))
|
|
286
|
+
and not [r for r in _audit_rows(R1)
|
|
287
|
+
if r.get("node_id") == "n212_ovr"], live=True)
|
|
288
|
+
# ①b 修前形态复刻:禁用探活 = 修前病灶(闸静默失效、无痕覆写)
|
|
289
|
+
_orig_probe = a1._maybe_reload_index
|
|
290
|
+
a1._maybe_reload_index = lambda: False
|
|
291
|
+
try:
|
|
292
|
+
b1.add("n212_red", ANCHOR_BODY, layer="anchor")
|
|
293
|
+
b1.flush()
|
|
294
|
+
ok2, exc2, _r2 = _raises(a1.add, "n212_red", FORGED, layer="anchor")
|
|
295
|
+
finally:
|
|
296
|
+
a1._maybe_reload_index = _orig_probe
|
|
297
|
+
check("1h 红形态复刻:禁用代际探活后写保护闸静默失效(不抛 ProtectionError)",
|
|
298
|
+
ok2 is True, "exc=%s" % exc2, live=True)
|
|
299
|
+
_fm2, _c2, _e2 = _disk_fm(R1, "n212_red", actor="n212")
|
|
300
|
+
check("1i 红形态复刻:伪造正文已无痕覆写盘面且零快照零审计",
|
|
301
|
+
FORGED in _c2
|
|
302
|
+
and not os.path.isdir(os.path.join(R1, protect.HISTORY_DIR,
|
|
303
|
+
"n212_red"))
|
|
304
|
+
and not [r for r in _audit_rows(R1)
|
|
305
|
+
if r.get("node_id") == "n212_red"],
|
|
306
|
+
repr(_c2)[:80], live=True)
|
|
307
|
+
|
|
308
|
+
# ================= ② N212 次生:同 id 双桶双文件 =================
|
|
309
|
+
print("\n【②】N212 次生:同 id 跨桶覆写必须只留一份文件(旧文件清理)")
|
|
310
|
+
a2 = _designer(R2)
|
|
311
|
+
a2.add("n212_seed2", "# 知识条目(丙):次生探针种子。\n", layer="knowledge")
|
|
312
|
+
a2.flush()
|
|
313
|
+
b2 = _designer(R2, actor="n212b2")
|
|
314
|
+
b2.add("n212_dup", DUP_B, layer="knowledge",
|
|
315
|
+
condition_space={"observation_position": "本地磁盘"})
|
|
316
|
+
b2.flush()
|
|
317
|
+
a2.add("n212_dup", DUP_A, layer="knowledge",
|
|
318
|
+
condition_space={"observation_position": "云端"})
|
|
319
|
+
_dups = _files_named(R2, "n212_dup")
|
|
320
|
+
check("2a 同 id 跨桶覆写后盘上恰一份文件(无孤儿副本)",
|
|
321
|
+
len(_dups) == 1, "files=%r" % _dups, live=True)
|
|
322
|
+
a2.flush()
|
|
323
|
+
a2.close()
|
|
324
|
+
_f2 = _designer(R2)
|
|
325
|
+
_n2 = _f2.get("n212_dup") or {}
|
|
326
|
+
check("2b 重开索引指向的文件内容 == 最后写入版本(无版本漂移/孤儿)",
|
|
327
|
+
DUP_A in (_n2.get("content") or "")
|
|
328
|
+
and len(_files_named(R2, "n212_dup")) == 1,
|
|
329
|
+
"content=%r files=%r" % ((_n2.get("content") or "")[:40],
|
|
330
|
+
_files_named(R2, "n212_dup")), live=True)
|
|
331
|
+
|
|
332
|
+
# ================= ③ N213:跨会话私密泄漏 =================
|
|
333
|
+
print("\n【③】N213 陈旧条目判可见性:同 actor 异 session 的 private 明文泄漏")
|
|
334
|
+
a3 = _reflect(R3, "S1")
|
|
335
|
+
a3.add("n213_priv", INTERNAL_BODY, layer="contextual") # internal/S1
|
|
336
|
+
a3.add("n213_ctrl", "# 情境记录(辛):共享档阳性对照。\n",
|
|
337
|
+
layer="contextual")
|
|
338
|
+
a3.flush() # serve 形态:autoflush=1,写后即落分片日志
|
|
339
|
+
b3 = _reflect(R3, "S2")
|
|
340
|
+
b3.add("n213_priv", PRIVATE_BODY, layer="contextual",
|
|
341
|
+
sensitivity="private") # private/S2
|
|
342
|
+
b3.flush()
|
|
343
|
+
_fm3, _c3, _e3 = _disk_fm(R3, "n213_priv")
|
|
344
|
+
check("3 setup:盘上真值已是 private/S2 且正文为密文",
|
|
345
|
+
_fm3.get("sensitivity") == "private" and _fm3.get("session") == "S2"
|
|
346
|
+
and crypto.is_encrypted(_c3),
|
|
347
|
+
"sens=%r sess=%r enc=%r" % (_fm3.get("sensitivity"),
|
|
348
|
+
_fm3.get("session"),
|
|
349
|
+
crypto.is_encrypted(_c3)))
|
|
350
|
+
_got = a3.get("n213_priv")
|
|
351
|
+
check("3a 异 session 读 private:get 必须 None(不返回明文)",
|
|
352
|
+
_got is None,
|
|
353
|
+
"leaked=%r" % ((_got or {}).get("content") or "")[:60], live=True)
|
|
354
|
+
a3._maybe_reload_index()
|
|
355
|
+
_cands = a3._candidates(layer="contextual")
|
|
356
|
+
_target = a3.index["nodes"].get("n213_priv")
|
|
357
|
+
_ctrl = a3.index["nodes"].get("n213_ctrl")
|
|
358
|
+
check("3b 候选面同闸:异 session 的 private 不进候选(条目身份比对)",
|
|
359
|
+
_target is not None
|
|
360
|
+
and not any(e is _target for e in _cands),
|
|
361
|
+
"cand=%d" % len(_cands), live=True)
|
|
362
|
+
check("3c 阳性对照:共享档(internal/S1)仍在候选中(闸未误伤)",
|
|
363
|
+
_ctrl is not None and any(e is _ctrl for e in _cands),
|
|
364
|
+
"cand=%d" % len(_cands), live=True)
|
|
365
|
+
# ③d 第三序面(本批首手实测:本条同时钉住 _open_content 的解密出口闸):
|
|
366
|
+
# 本进程**未 flush** 的陈旧条目在重载时被 _dirty 重放盖过盘上更新条目
|
|
367
|
+
# ⇒ 判据说 internal/S1、盘面已是 private/S2 ⇒ search 的正文水合把密文解成
|
|
368
|
+
# 明文返回(实测修复前 A.search 返回该节点且 content = 私密档明文)。
|
|
369
|
+
print("\n【③d】陈旧 _dirty 条目遮蔽他进程更新条目时的正文水合")
|
|
370
|
+
a6 = _reflect(R6, "S1")
|
|
371
|
+
a6.add("n213_d", INTERNAL_BODY, layer="contextual") # 故意不 flush
|
|
372
|
+
b6 = _reflect(R6, "S2")
|
|
373
|
+
b6.add("n213_d", PRIVATE_BODY, layer="contextual",
|
|
374
|
+
sensitivity="private")
|
|
375
|
+
b6.flush()
|
|
376
|
+
a6._maybe_reload_index()
|
|
377
|
+
_e6 = a6.index["nodes"].get("n213_d") or {}
|
|
378
|
+
check("3d setup:A 内存条目仍是本地未落盘版本(_dirty 重放遮蔽盘上真值)",
|
|
379
|
+
_e6.get("sensitivity") == "internal" and _e6.get("session") == "S1",
|
|
380
|
+
"sens=%r sess=%r" % (_e6.get("sensitivity"), _e6.get("session")))
|
|
381
|
+
check("3d A.get 同闸拒绝",
|
|
382
|
+
a6.get("n213_d") is None,
|
|
383
|
+
repr((a6.get("n213_d") or {}).get("content") or "")[:50], live=True)
|
|
384
|
+
_res6, _m6 = a6.search("跨会话泄漏探针")
|
|
385
|
+
_leak6 = [n.get("id") for n, _s, _q in _res6
|
|
386
|
+
if PRIVATE_BODY.strip() in str(n.get("content") or "")]
|
|
387
|
+
check("3d search 正文水合同闸:私密正文不以明文进入检索结果",
|
|
388
|
+
not _leak6, "leaked=%r" % (_leak6, ), live=True)
|
|
389
|
+
|
|
390
|
+
# ================= ④ N213 同根次生:protect 面陈旧条目 =================
|
|
391
|
+
print("\n【④】N213 同根:他进程新盖的 immutable/protected 对守卫不可见")
|
|
392
|
+
a4 = _designer(R4)
|
|
393
|
+
a4.add("n213_seed", "# 知识条目(丁):陈旧条目探针种子。\n",
|
|
394
|
+
layer="knowledge")
|
|
395
|
+
a4.flush()
|
|
396
|
+
b4 = _designer(R4, actor="n213b")
|
|
397
|
+
b4.add("n213_imm", IMM_BODY, layer="contextual", immutable=True)
|
|
398
|
+
b4.flush()
|
|
399
|
+
_imm4, _why4 = protect.is_immutable(a4, "n213_imm")
|
|
400
|
+
check("4a 他进程 immutable 标记可见(is_immutable=True)",
|
|
401
|
+
_imm4 is True, "is_immutable=%r/%r" % (_imm4, _why4), live=True)
|
|
402
|
+
ok4, exc4, _r4 = _raises(a4.add, "n213_imm", FORGED, layer="contextual")
|
|
403
|
+
check("4b 覆写他进程 immutable 节点被 ProtectionError 拒绝",
|
|
404
|
+
(not ok4) and exc4 == "ProtectionError", "exc=%s" % exc4, live=True)
|
|
405
|
+
_fm4, _c4, _e4 = _disk_fm(R4, "n213_imm", actor="n213b")
|
|
406
|
+
check("4c 拒绝后盘上正文未变", IMM_BODY in _c4 and FORGED not in _c4,
|
|
407
|
+
repr(_c4)[:80], live=True)
|
|
408
|
+
b4.add("n213_mark", MARK_BODY, layer="contextual")
|
|
409
|
+
b4.flush()
|
|
410
|
+
protect.mark(b4, "n213_mark", "N213 探针·显式保护标记")
|
|
411
|
+
_pr4, _pw4 = protect.is_protected(a4, "n213_mark")
|
|
412
|
+
check("4d protect.mark 的显式保护位对他进程可见(None ≠ 未标记)",
|
|
413
|
+
_pr4 is True and "N213 探针" in _pw4,
|
|
414
|
+
"is_protected=%r/%r" % (_pr4, _pw4), live=True)
|
|
415
|
+
ok4b, exc4b, _r4b = _raises(protect.guard_forget, a4, "n213_mark")
|
|
416
|
+
check("4e guard_forget 对 protect.mark 节点抛出 ProtectionError",
|
|
417
|
+
(not ok4b) and exc4b == "ProtectionError", "exc=%s" % exc4b, live=True)
|
|
418
|
+
|
|
419
|
+
# ================= ⑤ N224:重载后派生缓存必须失效 =================
|
|
420
|
+
print("\n【⑤】N224 重载后 chain/subgraph/trust 三个派生缓存不失效")
|
|
421
|
+
a5 = _reflect(R5, "S1", actor="n224")
|
|
422
|
+
a5.add("n224c", "# 知识条目(子):层级子节点。\n", layer="contextual")
|
|
423
|
+
a5.add("n224x", X_BODY, layer="contextual", importance=0.3)
|
|
424
|
+
a5.add("n224d", "# 知识条目(依):依赖 x 的单元。\n", layer="contextual",
|
|
425
|
+
depends_on=["n224x"])
|
|
426
|
+
a5.add("n224y", "# 知识条目(父):声明边与子层。\n", layer="contextual",
|
|
427
|
+
edges=[{"target": "n224x", "relation_type": "depends_on"}],
|
|
428
|
+
depends_on=["n224x"], subgraph={"nodes": ["n224c"]})
|
|
429
|
+
a5.flush()
|
|
430
|
+
_adj0 = chain.adjacency(a5)
|
|
431
|
+
check("5 setup:初始邻接表含 y→x 边",
|
|
432
|
+
any(t == "n224x" for t, _e in _adj0.get("n224y", [])),
|
|
433
|
+
"adj=%r" % (sorted(_adj0), ))
|
|
434
|
+
_deps0 = trust.dependents_index(a5)
|
|
435
|
+
check("5 setup:初始依赖反查含 x←d",
|
|
436
|
+
"n224d" in _deps0.get("n224x", []), str(_deps0)[:120])
|
|
437
|
+
_par0 = subgraph.parents_index(a5)
|
|
438
|
+
check("5 setup:初始父子反查含 c←y",
|
|
439
|
+
"n224y" in _par0.get("n224c", []), str(_par0)[:120])
|
|
440
|
+
b5 = _reflect(R5, "S2", actor="n224")
|
|
441
|
+
b5.add("n224x", PRIV_X, layer="contextual", sensitivity="private")
|
|
442
|
+
b5.flush()
|
|
443
|
+
b5.add("n224z", "# 知识条目(新):他进程新写入的依赖者。\n",
|
|
444
|
+
layer="contextual", depends_on=["n224y"],
|
|
445
|
+
edges=[{"target": "n224y", "relation_type": "depends_on"}])
|
|
446
|
+
b5.flush()
|
|
447
|
+
b5.add("n224g", "# 知识条目(新父):他进程新写入的层级父。\n",
|
|
448
|
+
layer="contextual", subgraph={"nodes": ["n224c"]})
|
|
449
|
+
b5.flush()
|
|
450
|
+
check("5a 探活:B 的新写入使 _maybe_reload_index 返回 True",
|
|
451
|
+
a5._maybe_reload_index() is True, "reloaded=False", live=True)
|
|
452
|
+
_adj1 = chain.adjacency(a5)
|
|
453
|
+
check("5b 关系链读隔离:已不可见节点 x 的出边与指向 x 的边整体消失",
|
|
454
|
+
"n224x" not in _adj1
|
|
455
|
+
and not [t for t, _e in _adj1.get("n224y", []) if t == "n224x"],
|
|
456
|
+
"y→x 仍在:%r" % (_adj1.get("n224y"), ), live=True)
|
|
457
|
+
check("5c 新快照节点进拓扑:他进程新写入的 z 出现在邻接表",
|
|
458
|
+
"n224z" in _adj1, "adj keys=%r" % sorted(_adj1), live=True)
|
|
459
|
+
_deps1 = trust.dependents_index(a5)
|
|
460
|
+
check("5d 依赖反查失效重算:新写入的 z 挂到 y 之下",
|
|
461
|
+
"n224z" in _deps1.get("n224y", []), str(_deps1)[:160], live=True)
|
|
462
|
+
_par1 = subgraph.parents_index(a5)
|
|
463
|
+
check("5e 父子反查失效重算:新写入的 g 成为 c 的父",
|
|
464
|
+
"n224g" in _par1.get("n224c", []), str(_par1)[:160], live=True)
|
|
465
|
+
|
|
466
|
+
# ================= ⑥ 源断言 + floor =================
|
|
467
|
+
print("\n【⑥】源断言(单点接线在位)")
|
|
468
|
+
_s_mdcg = _read_src("mdcg.py")
|
|
469
|
+
_sig = _src_window(_s_mdcg, "def _index_signature", "def _maybe_reload_index")
|
|
470
|
+
check("6a _index_signature 计入口径覆盖分片日志(不只 stat 快照)",
|
|
471
|
+
"index_log_dir" in _sig and "listdir" in _sig, _sig[-200:])
|
|
472
|
+
_fl = _src_window(_s_mdcg, "def flush", "def close")
|
|
473
|
+
check("6b flush 登记本进程分片(自身写入不计入代际签名)",
|
|
474
|
+
"_own_shard" in _fl, "_own_shard missing")
|
|
475
|
+
_ml = _src_window(_s_mdcg, "def _maybe_reload_index", "def _dir_fingerprint")
|
|
476
|
+
check("6c 重载路径失效 chain/subgraph/trust 三派生缓存",
|
|
477
|
+
"chain.invalidate_cache" in _ml and "subgraph.invalidate_cache" in _ml
|
|
478
|
+
and "trust.invalidate_cache" in _ml, _ml[-200:])
|
|
479
|
+
_s_mdcos = _read_src("mdcos.py")
|
|
480
|
+
_get = _src_window(_s_mdcos, "def get(self, node_id", "def search_rrf")
|
|
481
|
+
check("6d MdCGSecure.get 以盘上 frontmatter 复核可见性(陈旧条目不作准)",
|
|
482
|
+
'fm.get("sensitivity")' in _get and "_readable" in _get, _get[-200:])
|
|
483
|
+
_s_prot = _read_src("protect.py")
|
|
484
|
+
_pfm = _src_window(_s_prot, "def _fm", "def is_protected")
|
|
485
|
+
check("6e protect._fm 值为 None 时必须复核文件(受保护层,单点键集)",
|
|
486
|
+
"_GATE_UNKNOWN_KEYS" in _pfm, _pfm[-220:])
|
|
487
|
+
_pent = _src_window(_s_prot, "def _entry", "def _fm")
|
|
488
|
+
check("6f protect._entry 判定前代际探活(写/删/搬迁三面同闸)",
|
|
489
|
+
"_maybe_reload_index" in _pent, _pent[-160:])
|
|
490
|
+
_mk = _src_window(_s_prot, "def mark", "def stats")
|
|
491
|
+
check("6g protect.mark 改动进索引写日志(_dirty 标脏 + flush)",
|
|
492
|
+
"_dirty[node_id] = e" in _mk, _mk[-200:])
|
|
493
|
+
_oc = _src_window(_s_mdcos, "def _open_content", "# ---------- 索引")
|
|
494
|
+
check("6h 解密出口 _open_content 前置读可见性闸(全文面单点,含 search 水合)",
|
|
495
|
+
"read_denied" in _oc and "_readable" in _oc, _oc[-200:])
|
|
496
|
+
print(" [INFO] live 断言数 = %d(floor=%d)" % (LIVE, LIVE_FLOOR))
|
|
497
|
+
check("6i live 断言数不低于 floor(防空转绿)", LIVE >= LIVE_FLOOR,
|
|
498
|
+
"live=%d" % LIVE)
|
|
499
|
+
|
|
500
|
+
print("\n" + "=" * 64)
|
|
501
|
+
print("PASS=%d FAIL=%d LIVE=%d" % (PASS, FAIL, LIVE))
|
|
502
|
+
if FAILS:
|
|
503
|
+
print("失败项:" + ";".join(FAILS))
|
|
504
|
+
return 0 if FAIL == 0 else 1
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
if __name__ == "__main__":
|
|
508
|
+
try:
|
|
509
|
+
rc = main()
|
|
510
|
+
finally:
|
|
511
|
+
_close_all()
|
|
512
|
+
time.sleep(0.05)
|
|
513
|
+
sys.exit(rc)
|