@furongjun1999/dsh-memory 0.6.0 → 0.7.0
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 +618 -572
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/README.md +1 -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/207/272/350/264/247/351/235/242/345/206/222/347/203/237_/350/277/233/350/264/247/351/227/250/347/246/201_v1.0.md +460 -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//345/217/221/345/270/20308_/350/207/252/350/277/255/344/273/243/344/270/216/347/235/241/347/234/240_/345/233/276/346/243/200/347/264/242/350/267/257/344/270/216/346/235/203/351/207/215_v1.0.md +97 -0
- package/docs/eval//345/275/222/344/270/200/345/261/202/347/274/272/347/234/201/347/277/273/345/205/263_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +458 -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/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +136 -11
- 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 +52 -2
- 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 +100 -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 +57 -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//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +183 -0
- 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//347/235/241/347/234/240/344/270/216/350/207/252/350/277/255/344/273/243_/345/212/237/350/203/275/344/274/230/345/214/226/350/256/276/350/256/241_v0.4.md +547 -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_e2e_locomo_qa.py +11 -4
- 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 +48 -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/freshness.py +511 -0
- package/md_cg/fsutil.py +454 -8
- package/md_cg/generation.py +409 -0
- package/md_cg/hotcache.py +4 -1
- 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 +4261 -3818
- package/md_cg/mdcg.py +4695 -3547
- package/md_cg/mdcos.py +4672 -4159
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +713 -576
- 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/semantic/canonical.py +22 -0
- package/md_cg/semantic/unify.py +73 -22
- package/md_cg/semantic/unify_fixture.json +25 -0
- package/md_cg/sleep.py +1297 -0
- 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 +1436 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_auto_defaults.py +424 -0
- 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_boundary_hit.py +410 -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_en_pipeline.py +22 -13
- package/md_cg/test_generation_guard.py +352 -0
- 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 +1387 -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 +519 -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_n230_dirty_replay.py +375 -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_p2_six_elements.py +463 -0
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- package/md_cg/test_p3_legacy_closure.py +433 -0
- package/md_cg/test_p4_freshness.py +680 -0
- package/md_cg/test_p8_subgraph_chain.py +14 -1
- 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 +522 -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_semantic_canonical.py +5 -3
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_sleep.py +611 -0
- package/md_cg/test_sleep_p1.py +784 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_time_core_lint.py +968 -0
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_unify_default_off.py +701 -0
- package/md_cg/test_unify_scope.py +182 -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/aeis_core/time_core.py +8 -0
- 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 +4 -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/utf8_boot.py +237 -0
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 会话要点读出口可见性守卫(N202,restricted 档要点明文回给处置链路之外身份)
|
|
3
|
+
|
|
4
|
+
行号按修复后(本批 mdcos.py 因 +26 行 `_note_visible` 与闸位注释整体下移)。
|
|
5
|
+
|
|
6
|
+
缺陷(修复前):`md_cg/mdcos.py:2548 _session_notes` 全函数体**零 `_readable`
|
|
7
|
+
调用**——它只按 tag 过滤(SESSION_TAG / `session:`)后 `self._read(e)` 回读正文
|
|
8
|
+
(:2573),`content is None` 才 continue(原 :2532-2533,注释自述「不可读
|
|
9
|
+
(无密钥 / 身份不符)→ 视为不存在」)。而 `_read`(mdcg.py:2445-2454)只在
|
|
10
|
+
**文件缺失/读失败**时返回 None:`restricted` 档按设计**不加密、盘上明文**
|
|
11
|
+
(同族断言见 md_cg/test_read_scope_b27.py:215「restricted 落盘不加密」),故该
|
|
12
|
+
continue 分支对「身份不可读」**永不生效**——可见性单点 `_readable`(:3973)被
|
|
13
|
+
get(:4087)/search_rrf(:4104)/list_goals(:4026)/_chain_visible(:4020)/_candidates
|
|
14
|
+
(:4070) 消费(单点健全),唯独这个读出口没接线(`session_recall` ① 段直接取它
|
|
15
|
+
的结果)。
|
|
16
|
+
|
|
17
|
+
后果:持 session op 的低密级身份(tokens.ROLE_SPECS 里 record/reflect/output/
|
|
18
|
+
sustain 的 ops_allow 含 "session" 而 `can_read_restricted=False`)调
|
|
19
|
+
`cg(op=session, action=recall)`(工具面 md_cg/mcp_server.py:2607-2614 →
|
|
20
|
+
mdcos.session_recall)即可拿到 restricted 档会话要点的**正文摘要明文**
|
|
21
|
+
(`_session_digest` 取 `# 执行:` 字段,:2498);同一节点经 get/search
|
|
22
|
+
已正确拒绝(B27-6d/6e/6f),即「同一库两条出口口径不一致」。restricted =
|
|
23
|
+
错误处置标记(批次 28 分型:密级底线 internal ∧ 处置链路角色
|
|
24
|
+
designer/orchestr/verify 或 can_admin)。
|
|
25
|
+
|
|
26
|
+
修复(最小改动 · fail-closed,单点接线):`_session_notes` 的 tag 过滤之后、
|
|
27
|
+
`_read` 之前断言可见性——判据取**同一个 `_readable`**(同一 `self.principal`),
|
|
28
|
+
故两侧口径**必然一致**;处置链路身份与同密级(internal/public)要点零影响。
|
|
29
|
+
取用方式为仓内既有的**可选钩子**口径(`_note_visible`,mdcos.py 模块级):
|
|
30
|
+
`_readable` 定义在**子类** `MdCGSecure`,读数出口却在基类 `MdCGOS`——直调会让
|
|
31
|
+
纯 MdCGOS 实例(无身份/密级模型)抛 AttributeError 并被上层 except 吞成
|
|
32
|
+
degraded(修中实测:md_cg/test_p29_session_ingest_export 因此转红,见 ⑩ 组)。
|
|
33
|
+
同族第二出口(**本守卫实跑发现,N211**):`session_recall` ④ 未解问题段同一
|
|
34
|
+
根因(只按 layer 过滤 → 直读正文 → 抽 `# 问题:`),restricted 档问题正文同样
|
|
35
|
+
明文外泄——同批接同一判据。
|
|
36
|
+
|
|
37
|
+
守卫(⓿+十一组,全部临时库 + 哑主密钥 + 临时令牌库,**绝不触真实 ~/.mdcg**):
|
|
38
|
+
⓿ 隔离自证:aux 根与令牌库都落在守卫临时目录(否则中止,不带病跑);
|
|
39
|
+
① 红转绿核心:record 身份 session_recall 的 notes 不含 restricted 正文摘要
|
|
40
|
+
(修前 = 命中「RESTRICTED-ABC-def」即红),但仍含 internal 要点(反向腿:
|
|
41
|
+
防「一刀切清空」式过度收紧);
|
|
42
|
+
② 单点直调:`_session_notes(session=None/session=SESS)` 两形态同闸;
|
|
43
|
+
③ 工具面真身:`mcp_server._session_call(cg, {action:"recall"})`(op=session
|
|
44
|
+
分发的实际出口)同闸;
|
|
45
|
+
④ 口径一致性:同身份下 `cg.get(restricted_id)` 为 None(既有行为)∧ notes
|
|
46
|
+
亦不含该要点(修前两条出口矛盾);
|
|
47
|
+
⑤ 攻击面可达性(第一手,替代材料二手结论):record/reflect/output/sustain
|
|
48
|
+
四角色 session ∈ ops_allow ∧ can_read_restricted=False;
|
|
49
|
+
⑥ 正控(防过度收紧):designer/orchestr/verify(处置链路)仍看得到 restricted
|
|
50
|
+
要点;
|
|
51
|
+
⑦ stdio 端到端(与缺陷材料同口径):临时令牌库签发 record 令牌 →
|
|
52
|
+
`python -X utf8 -m md_cg.mcp_server` 上 `cg(op=session,action=recall)`,
|
|
53
|
+
响应 JSON 的 notes 不含 restricted 摘要(修前 = 明文泄漏)、含 internal 要点;
|
|
54
|
+
⑧ 同族第二出口(N211):④ 未解问题段的 restricted 问题正文不外泄、
|
|
55
|
+
internal 问题仍返回;
|
|
56
|
+
⑨ 判据确为闸门:置 `MdCGCSecure._readable` 恒真(= 修前形态)→ 两出口泄漏
|
|
57
|
+
双双复现,还原后重新闭合(证明判据就是闸门,非「碰巧空」);
|
|
58
|
+
⑩ 纯 MdCGOS(无 `_readable` 钩子)不得因本修复降级:notes 正常返回、
|
|
59
|
+
degraded 段无 notes/unresolved(p29 形态回归);
|
|
60
|
+
⑪ 源断言 + floor:两处出口都经 `_note_visible`、判据在 `_read` 之前、
|
|
61
|
+
钩子取用走 getattr 且异常按不可见;实弹断言计数下限。
|
|
62
|
+
|
|
63
|
+
运行:python -X utf8 -m md_cg.test_n202_session_notes_visibility
|
|
64
|
+
"""
|
|
65
|
+
from __future__ import annotations
|
|
66
|
+
|
|
67
|
+
import atexit
|
|
68
|
+
import json
|
|
69
|
+
import os
|
|
70
|
+
import shutil
|
|
71
|
+
import subprocess
|
|
72
|
+
import sys
|
|
73
|
+
import tempfile
|
|
74
|
+
import time
|
|
75
|
+
|
|
76
|
+
# ── 隔离前置:必须在 import md_cg 子模块**之前**(tokens/theory/links/signer/
|
|
77
|
+
# evidence 在模块级把 aux_root() 冻结成常量,md_cg/datapath.py:254-268 逐字读
|
|
78
|
+
# env)——否则会指到真实 ~/.mdcg 并在那里建密钥/令牌库。 ──
|
|
79
|
+
_TMP = tempfile.mkdtemp(prefix="n202_")
|
|
80
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
81
|
+
os.environ["MDCG_MASTER_KEY"] = "cd" * 32 # 哑主密钥(纪律:绝不触真实密钥面)
|
|
82
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP, "tokens.json") # 临时令牌库
|
|
83
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot") # 防任何回落
|
|
84
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
85
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
86
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_STATE_ROOT",
|
|
87
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES",
|
|
88
|
+
"PYTHONIOENCODING", "PYTHONUTF8"):
|
|
89
|
+
os.environ.pop(_k, None)
|
|
90
|
+
|
|
91
|
+
from . import mcp_server, tokens # noqa: E402
|
|
92
|
+
from .datapath import aux_root # noqa: E402
|
|
93
|
+
from .mdcos import MdCGOS, MdCGSecure # noqa: E402
|
|
94
|
+
from .security import Principal, can_read_restricted # noqa: E402
|
|
95
|
+
|
|
96
|
+
# 退场清洁(机制说明,非本次修复面):库层 `mdcg._atexit_flush_all`(mdcg.py:104-111)
|
|
97
|
+
# 在进程退出时对**仍存活实例**补一次 close() → compact/rebuild 会把 `_index.json`
|
|
98
|
+
# 写回守卫刚删掉的临时根(实测残留 `<tmp>/n202_*/cgroot/_index.json{,.lock}` 61 字节)。
|
|
99
|
+
# atexit 为 LIFO:本处**在任何 MdCG 实例创建之前**注册(早于库层钩子注册时机)→
|
|
100
|
+
# 退出时**最后执行**,故最终状态清净。显式清理在 main 收尾也做一次(正常路径即时清)。
|
|
101
|
+
atexit.register(lambda: _cleanup(_TMP))
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
105
|
+
except AttributeError:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
PASS = FAIL = 0
|
|
109
|
+
LIVE = 0
|
|
110
|
+
FAILS = []
|
|
111
|
+
LIVE_FLOOR = 4
|
|
112
|
+
|
|
113
|
+
SECRET = "限制级会话要点-RESTRICTED-ABC-def" # restricted 要点(泄漏探针)
|
|
114
|
+
PLAIN = "常规会话要点-INTERNAL-XYZ-ghi" # internal 要点(反向腿锚)
|
|
115
|
+
Q_SECRET = "限制级待解问题-RESTRICTED-QQQ-xyz" # restricted 未解问题(第二出口探针)
|
|
116
|
+
Q_PLAIN = "常规待解问题-INTERNAL-QQQ-zzz"
|
|
117
|
+
SESS = "session-n202"
|
|
118
|
+
ATTACK_ROLES = ("record", "reflect", "output", "sustain")
|
|
119
|
+
CHAIN_ROLES = (("designer", "secret", True), ("orchestr", "internal", False),
|
|
120
|
+
("verify", "internal", False))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def check(name, cond, detail="", live=False):
|
|
124
|
+
global PASS, FAIL, LIVE
|
|
125
|
+
if live:
|
|
126
|
+
LIVE += 1
|
|
127
|
+
if cond:
|
|
128
|
+
PASS += 1
|
|
129
|
+
print(" [PASS] %s" % name)
|
|
130
|
+
else:
|
|
131
|
+
FAIL += 1
|
|
132
|
+
FAILS.append(name)
|
|
133
|
+
print(" [FAIL] %s · %s" % (name, detail))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _princ(role, clearance="internal", can_admin=False, session=None):
|
|
137
|
+
spec = tokens.role_spec(role)
|
|
138
|
+
return Principal(tenant="default", actor="n202-" + role, clearance=clearance,
|
|
139
|
+
can_write=bool(spec["can_write"]), can_admin=can_admin,
|
|
140
|
+
role=role, layers_allow=spec["layers_allow"],
|
|
141
|
+
ops_allow=spec["ops_allow"], auth_mode="test",
|
|
142
|
+
session=session)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _notes_of(pack):
|
|
146
|
+
return [n.get("summary") or "" for n in (pack.get("notes") or [])]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _has(texts, needle):
|
|
150
|
+
return any(needle in (t or "") for t in texts)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _src_window(src, start, end):
|
|
154
|
+
i = src.find(start)
|
|
155
|
+
if i < 0:
|
|
156
|
+
return ""
|
|
157
|
+
j = src.find(end, i)
|
|
158
|
+
return src[i:] if j < 0 else src[i:j]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _cleanup(path) -> bool:
|
|
162
|
+
"""删守卫临时根(Windows 句柄释放有延迟:重试 + gc),失败如实上报。"""
|
|
163
|
+
import gc
|
|
164
|
+
for _ in range(6):
|
|
165
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
166
|
+
if not os.path.exists(path):
|
|
167
|
+
return True
|
|
168
|
+
gc.collect()
|
|
169
|
+
time.sleep(0.25)
|
|
170
|
+
return not os.path.exists(path)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_OPEN = [] # 本守卫创建的图实例:临时根清理前显式 close(释放索引锁句柄)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _secured(root, principal):
|
|
177
|
+
cg = MdCGSecure(root, principal=principal)
|
|
178
|
+
_OPEN.append(cg)
|
|
179
|
+
return cg
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _close_all():
|
|
183
|
+
for cg in _OPEN:
|
|
184
|
+
try:
|
|
185
|
+
cg.close()
|
|
186
|
+
except Exception: # noqa: BLE001 —— 清理面不得再抛
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
global LIVE
|
|
192
|
+
# ---------- ⓿ 隔离自证(fail-closed:未隔离即中止,不带病跑) ----------
|
|
193
|
+
print("[0] 隔离自证")
|
|
194
|
+
aux = aux_root()
|
|
195
|
+
aux_ok = os.path.abspath(aux).lower().startswith(
|
|
196
|
+
os.path.abspath(_TMP).lower())
|
|
197
|
+
tokfile = tokens.token_file()
|
|
198
|
+
tok_ok = os.path.abspath(tokfile).lower().startswith(
|
|
199
|
+
os.path.abspath(_TMP).lower())
|
|
200
|
+
check("0a aux 根(密钥/信任/理论面)落在守卫临时目录", aux_ok, "aux=%s" % aux)
|
|
201
|
+
check("0b 令牌库落在守卫临时目录(绝不触真实凭据)", tok_ok,
|
|
202
|
+
"tokfile=%s" % tokfile)
|
|
203
|
+
if not (aux_ok and tok_ok):
|
|
204
|
+
print("FATAL: 隔离失败,中止(绝不带病跑真实 ~/.mdcg)")
|
|
205
|
+
return 2
|
|
206
|
+
|
|
207
|
+
root = os.path.join(_TMP, "cgroot")
|
|
208
|
+
seed = _secured(root, _princ("designer", clearance="secret",
|
|
209
|
+
can_admin=True))
|
|
210
|
+
r_secret = seed.session_note(SECRET, session=SESS, sensitivity="restricted",
|
|
211
|
+
basis="test")
|
|
212
|
+
r_plain = seed.session_note(PLAIN, session=SESS, sensitivity="internal",
|
|
213
|
+
basis="test")
|
|
214
|
+
# ④ 段(未解问题)的 restricted 探针:同族第二出口(N211)
|
|
215
|
+
seed.add("unres_n209_secret",
|
|
216
|
+
"# 功能名:待解问题样本\n# 生效条件:不限\n# 子功能:待解\n"
|
|
217
|
+
"# 执行:探针\n# 验证方式:test\n# 不适用条件:无\n"
|
|
218
|
+
"# 问题:" + Q_SECRET + "\n",
|
|
219
|
+
layer="unresolved", sensitivity="restricted")
|
|
220
|
+
seed.add("unres_n209_plain",
|
|
221
|
+
"# 功能名:待解问题样本二\n# 生效条件:不限\n# 子功能:待解\n"
|
|
222
|
+
"# 执行:探针\n# 验证方式:test\n# 不适用条件:无\n"
|
|
223
|
+
"# 问题:" + Q_PLAIN + "\n",
|
|
224
|
+
layer="unresolved", sensitivity="internal")
|
|
225
|
+
seed.flush()
|
|
226
|
+
sid_secret, sid_plain = r_secret["id"], r_plain["id"]
|
|
227
|
+
|
|
228
|
+
# ---------- ① 红转绿核心:record 的 session_recall ----------
|
|
229
|
+
print("[1] record 身份经 session_recall 取会话要点(session=None 与指定 session 两形态)")
|
|
230
|
+
rec = _secured(root, _princ("record"))
|
|
231
|
+
# 先确认库层该身份对 restricted 节点的既有正确行为(对照面,不得回归)
|
|
232
|
+
check("1a 对照面:record 直接 get(restricted 要点) 必为 None(既有 _readable 行为)",
|
|
233
|
+
rec.get(sid_secret) is None)
|
|
234
|
+
pack_none = rec.session_recall(session=None, limit=5)
|
|
235
|
+
sums_none = _notes_of(pack_none)
|
|
236
|
+
check("1b session=None:notes 不含 restricted 正文摘要(修前 = 明文命中)",
|
|
237
|
+
not _has(sums_none, "RESTRICTED"),
|
|
238
|
+
"notes=%s" % [s[:60] for s in sums_none], live=True)
|
|
239
|
+
check("1c session=None:notes 仍含 internal 要点(反向腿:非一刀切清空)",
|
|
240
|
+
_has(sums_none, "INTERNAL"),
|
|
241
|
+
"notes=%s" % [s[:60] for s in sums_none], live=True)
|
|
242
|
+
pack_sess = rec.session_recall(session=SESS, limit=5)
|
|
243
|
+
sums_sess = _notes_of(pack_sess)
|
|
244
|
+
check("1d session=<归属会话>:restricted 摘要同样不为该身份返回",
|
|
245
|
+
not _has(sums_sess, "RESTRICTED"),
|
|
246
|
+
"notes=%s" % [s[:60] for s in sums_sess], live=True)
|
|
247
|
+
check("1e 未降级:degraded 段无 notes(不是异常被吞成空)",
|
|
248
|
+
"notes" not in (pack_none.get("degraded") or []),
|
|
249
|
+
"degraded=%s" % (pack_none.get("degraded"),))
|
|
250
|
+
|
|
251
|
+
# ---------- ② 单点直调(_session_notes) ----------
|
|
252
|
+
print("[2] _session_notes 直调同闸(读数单点接线)")
|
|
253
|
+
d_none = rec._session_notes(session=None, limit=5)
|
|
254
|
+
check("2a 直调 session=None:不含 restricted 摘要且含 internal 要点",
|
|
255
|
+
not _has([n.get("summary") for n in d_none], "RESTRICTED")
|
|
256
|
+
and _has([n.get("summary") for n in d_none], "INTERNAL"),
|
|
257
|
+
"notes=%s" % [n.get("summary", "")[:50] for n in d_none], live=True)
|
|
258
|
+
|
|
259
|
+
# ---------- ③ 工具面真身(op=session → _session_call) ----------
|
|
260
|
+
print("[3] 工具面出口 _session_call(action=recall)")
|
|
261
|
+
out = mcp_server._session_call(rec, {"action": "recall", "session": None,
|
|
262
|
+
"limit": 5})
|
|
263
|
+
osums = _notes_of(out)
|
|
264
|
+
check("3a op=session 出口不含 restricted 摘要(修前 = 工具面明文泄漏)",
|
|
265
|
+
not _has(osums, "RESTRICTED"),
|
|
266
|
+
"notes=%s" % [s[:60] for s in osums], live=True)
|
|
267
|
+
check("3b op=session 出口仍含 internal 要点", _has(osums, "INTERNAL"),
|
|
268
|
+
"notes=%s" % [s[:60] for s in osums], live=True)
|
|
269
|
+
|
|
270
|
+
# ---------- ④ 两条出口口径一致(get vs session_recall) ----------
|
|
271
|
+
print("[4] 口径一致性(可见性单点 vs 会话读数出口)")
|
|
272
|
+
check("4a get(restricted)=None ∧ session_recall 亦不含 → 两出口一致(修前矛盾)",
|
|
273
|
+
rec.get(sid_secret) is None and not _has(sums_none, "RESTRICTED"),
|
|
274
|
+
"get=%r notes=%s" % (rec.get(sid_secret), [s[:40] for s in sums_none]),
|
|
275
|
+
live=True)
|
|
276
|
+
|
|
277
|
+
# ---------- ⑤ 攻击面可达性(第一手) ----------
|
|
278
|
+
print("[5] 攻击身份可达性:session ∈ ops_allow ∧ can_read_restricted=False")
|
|
279
|
+
for role in ATTACK_ROLES:
|
|
280
|
+
spec = tokens.role_spec(role)
|
|
281
|
+
p = _princ(role)
|
|
282
|
+
check("5·%s session 在 ops_allow 且 restricted 链路角色缺位"
|
|
283
|
+
% role,
|
|
284
|
+
"session" in (spec["ops_allow"] or [])
|
|
285
|
+
and can_read_restricted(p) is False,
|
|
286
|
+
"ops_allow_has_session=%s can_read_restricted=%s"
|
|
287
|
+
% ("session" in (spec["ops_allow"] or []), can_read_restricted(p)))
|
|
288
|
+
entry = (seed.index.get("nodes") or {}).get(sid_secret) or {}
|
|
289
|
+
check("5·%s 该身份处 _readable(restricted 要点) 确为 False"
|
|
290
|
+
"(修前 session_recall 未消费此判据)" % role,
|
|
291
|
+
bool(entry) and _secured(root, p)._readable(
|
|
292
|
+
dict(entry)) is False,
|
|
293
|
+
"entry_sens=%s" % entry.get("sensitivity"))
|
|
294
|
+
|
|
295
|
+
# ---------- ⑥ 正控:处置链路身份仍可见(防过度收紧) ----------
|
|
296
|
+
print("[6] 正控:designer/orchestr/verify 仍读得到 restricted 要点")
|
|
297
|
+
for role, cl, adm in CHAIN_ROLES:
|
|
298
|
+
cgc = _secured(root, _princ(role, clearance=cl, can_admin=adm))
|
|
299
|
+
csums = _notes_of(cgc.session_recall(session=None, limit=5))
|
|
300
|
+
check("6·%s 处置链路仍见 restricted 摘要(未误伤必读面)" % role,
|
|
301
|
+
_has(csums, "RESTRICTED"),
|
|
302
|
+
"notes=%s" % [s[:50] for s in csums], live=True)
|
|
303
|
+
|
|
304
|
+
# ---------- ⑦ stdio 端到端 ----------
|
|
305
|
+
print("[7] stdio 端到端:临时 record 令牌 → cg(op=session,action=recall)")
|
|
306
|
+
issued = tokens.issue("record", actor="n202-rec")
|
|
307
|
+
env = {k: v for k, v in os.environ.items() if not k.startswith("MDCG_")}
|
|
308
|
+
env.update({
|
|
309
|
+
"MDCG_ROOT": root,
|
|
310
|
+
"MDCG_AUX_ROOT": os.environ["MDCG_AUX_ROOT"],
|
|
311
|
+
"MDCG_MASTER_KEY": os.environ["MDCG_MASTER_KEY"],
|
|
312
|
+
"MDCG_TOKEN_FILE": os.environ["MDCG_TOKEN_FILE"],
|
|
313
|
+
"MDCG_TOKEN": issued["token"],
|
|
314
|
+
"MDCG_TENANT_REGISTRY": os.path.join(_TMP, "_tenants_absent.json"),
|
|
315
|
+
"MDCG_STATE_ROOT": os.path.join(_TMP, "state"),
|
|
316
|
+
"MDCG_DATA_ROOT": os.path.join(_TMP, "data"),
|
|
317
|
+
"MDCG_SUSTAIN": "0",
|
|
318
|
+
})
|
|
319
|
+
lines = [json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize"}),
|
|
320
|
+
json.dumps({"jsonrpc": "2.0", "id": 2, "method": "tools/call",
|
|
321
|
+
"params": {"name": "cg", "arguments": {
|
|
322
|
+
"op": "session", "action": "recall",
|
|
323
|
+
"session": None, "limit": 5}}}),
|
|
324
|
+
json.dumps({"jsonrpc": "2.0", "id": 3, "method": "shutdown"})]
|
|
325
|
+
proc = subprocess.Popen(
|
|
326
|
+
[sys.executable, "-X", "utf8", "-m", "md_cg.mcp_server"],
|
|
327
|
+
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
328
|
+
env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
329
|
+
stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace")
|
|
330
|
+
try:
|
|
331
|
+
out_s, err_s = proc.communicate("\n".join(lines) + "\n", timeout=180)
|
|
332
|
+
except subprocess.TimeoutExpired:
|
|
333
|
+
proc.kill()
|
|
334
|
+
out_s, err_s = proc.communicate()
|
|
335
|
+
# 自报戳残留清理(诊断设施,非本次修复面):只删本子进程自己的那份
|
|
336
|
+
try:
|
|
337
|
+
os.remove(os.path.join(tempfile.gettempdir(), "md_cg_servers",
|
|
338
|
+
"%d.json" % proc.pid))
|
|
339
|
+
except OSError:
|
|
340
|
+
pass
|
|
341
|
+
resp = None
|
|
342
|
+
for ln in (out_s or "").splitlines():
|
|
343
|
+
ln = ln.strip()
|
|
344
|
+
if not ln:
|
|
345
|
+
continue
|
|
346
|
+
try:
|
|
347
|
+
o = json.loads(ln)
|
|
348
|
+
except ValueError:
|
|
349
|
+
continue
|
|
350
|
+
if isinstance(o, dict) and o.get("id") == 2:
|
|
351
|
+
resp = o
|
|
352
|
+
payload = {}
|
|
353
|
+
if isinstance(resp, dict):
|
|
354
|
+
try:
|
|
355
|
+
payload = json.loads(
|
|
356
|
+
(resp.get("result") or {}).get("content", [{}])[0].get(
|
|
357
|
+
"text", "{}"))
|
|
358
|
+
except (ValueError, AttributeError, IndexError):
|
|
359
|
+
payload = {}
|
|
360
|
+
esums = _notes_of(payload)
|
|
361
|
+
check("7a 端到端:record 令牌的 op=session 响应不含 restricted 摘要"
|
|
362
|
+
"(修前 = 整段明文泄漏)",
|
|
363
|
+
proc.returncode == 0 and isinstance(payload, dict)
|
|
364
|
+
and not _has(esums, "RESTRICTED"),
|
|
365
|
+
"rc=%s notes=%s err_tail=%r" % (proc.returncode,
|
|
366
|
+
[s[:60] for s in esums], (err_s or "")[-200:]),
|
|
367
|
+
live=True)
|
|
368
|
+
check("7b 端到端:同响应仍含 internal 要点(出口本身仍工作)",
|
|
369
|
+
_has(esums, "INTERNAL"),
|
|
370
|
+
"notes=%s" % [s[:60] for s in esums], live=True)
|
|
371
|
+
|
|
372
|
+
# ---------- ⑧ 同族第二出口:④ 未解问题段(N211,本守卫实跑发现) ----------
|
|
373
|
+
print("[8] session_recall ④ 未解问题段(同族第二出口,N211)")
|
|
374
|
+
unres = [u.get("question") or "" for u in (pack_none.get("unresolved") or [])]
|
|
375
|
+
check("8a ④ 段不含 restricted 未解问题正文(修前 = 明文命中)",
|
|
376
|
+
not _has(unres, "RESTRICTED-QQQ"),
|
|
377
|
+
"unresolved=%s" % [u[:60] for u in unres], live=True)
|
|
378
|
+
check("8b ④ 段仍含 internal 未解问题(反向腿:非一刀切清空)",
|
|
379
|
+
_has(unres, "INTERNAL-QQQ"),
|
|
380
|
+
"unresolved=%s" % [u[:60] for u in unres], live=True)
|
|
381
|
+
|
|
382
|
+
# ---------- ⑨ 修前病灶复刻:置 _readable 恒真 → 两出口泄漏复现 ----------
|
|
383
|
+
print("[9] 判据确为闸门(置 _readable 恒真 = 修前形态,泄漏必须复现)")
|
|
384
|
+
_orig_readable = MdCGSecure._readable
|
|
385
|
+
MdCGSecure._readable = lambda self, e, session=None: True
|
|
386
|
+
try:
|
|
387
|
+
m_notes = _notes_of(rec.session_recall(session=None, limit=5))
|
|
388
|
+
m_unres = [u.get("question") or ""
|
|
389
|
+
for u in (rec.session_recall(session=None, limit=5).get(
|
|
390
|
+
"unresolved") or [])]
|
|
391
|
+
finally:
|
|
392
|
+
MdCGSecure._readable = _orig_readable
|
|
393
|
+
check("9a 置恒真后 notes 泄漏复现(证明 ① 段判据就是 _readable)",
|
|
394
|
+
_has(m_notes, "RESTRICTED"), "notes=%s" % [s[:50] for s in m_notes])
|
|
395
|
+
check("9b 置恒真后 unresolved 泄漏复现(证明 ④ 段判据同源)",
|
|
396
|
+
_has(m_unres, "RESTRICTED-QQQ"),
|
|
397
|
+
"unresolved=%s" % [u[:50] for u in m_unres])
|
|
398
|
+
check("9c 还原后两出口重新闭合(闸可逆、无残留状态)",
|
|
399
|
+
not _has(_notes_of(rec.session_recall(session=None, limit=5)),
|
|
400
|
+
"RESTRICTED")
|
|
401
|
+
and not _has([u.get("question") or "" for u in
|
|
402
|
+
(rec.session_recall(session=None, limit=5).get(
|
|
403
|
+
"unresolved") or [])], "RESTRICTED-QQQ"))
|
|
404
|
+
|
|
405
|
+
# ---------- ⑩ 纯 MdCGOS(无 _readable 钩子)不得因本修复降级 ----------
|
|
406
|
+
print("[10] 纯 MdCGOS 实例(无 _readable:无身份/密级模型)读数出口不降级")
|
|
407
|
+
gos = MdCGOS(root, actor="n202-gos")
|
|
408
|
+
_OPEN.append(gos)
|
|
409
|
+
gos.principal = _princ("designer", clearance="secret", can_admin=True)
|
|
410
|
+
gp = gos.session_recall(session=None, limit=5)
|
|
411
|
+
g_notes = _notes_of(gp)
|
|
412
|
+
check("10a 纯 MdCGOS:notes 正常返回(不得因调用不存在的 _readable 而整段降级)",
|
|
413
|
+
_has(g_notes, "INTERNAL"), "notes=%s degraded=%s"
|
|
414
|
+
% ([s[:40] for s in g_notes], gp.get("degraded")), live=True)
|
|
415
|
+
check("10b 纯 MdCGOS:degraded 段无 notes/unresolved(无异常被吞)",
|
|
416
|
+
not ({"notes", "unresolved"} & set(gp.get("degraded") or [])),
|
|
417
|
+
"degraded=%s" % (gp.get("degraded"),), live=True)
|
|
418
|
+
|
|
419
|
+
# ---------- ⑪ 源断言 + floor ----------
|
|
420
|
+
print("[11] 源断言与自检")
|
|
421
|
+
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
422
|
+
"mdcos.py"), encoding="utf-8", errors="replace") as fh:
|
|
423
|
+
src = fh.read()
|
|
424
|
+
body = _src_window(src, "def _session_notes", "def session_recall")
|
|
425
|
+
check("11a _session_notes 函数体内接可见性判据(修前 = 零调用)",
|
|
426
|
+
"_note_visible(self, e)" in body, "window_len=%d" % len(body))
|
|
427
|
+
check("11b 判据取用在 _read 之前(fail-closed 序)",
|
|
428
|
+
0 <= body.find("_note_visible(self, e)") < body.find("self._read(e)"),
|
|
429
|
+
"guard_at=%d read_at=%d" % (body.find("_note_visible(self, e)"),
|
|
430
|
+
body.find("self._read(e)")))
|
|
431
|
+
helper = _src_window(src, "def _note_visible", "def _session_notes")
|
|
432
|
+
check("11c 判据取用走可选钩子 getattr(cg, \"_readable\") 且异常按不可见"
|
|
433
|
+
"(不误伤纯 MdCGOS,不 fail-open 判据异常)",
|
|
434
|
+
"_readable" in helper and "getattr" in helper,
|
|
435
|
+
"helper_len=%d" % len(helper))
|
|
436
|
+
check("11d ④ 未解问题段同接该判据(同族第二出口已收口)",
|
|
437
|
+
"_note_visible(self, e)" in _src_window(
|
|
438
|
+
src, "# ④ 未解问题", "# ⑤ 自我状态卡"),
|
|
439
|
+
"window_len=%d" % len(_src_window(src, "# ④ 未解问题",
|
|
440
|
+
"# ⑤ 自我状态卡")))
|
|
441
|
+
check("11e 实弹断言数 ≥ %d(防 spawn/扫描面失效假绿)" % LIVE_FLOOR,
|
|
442
|
+
LIVE >= LIVE_FLOOR, "LIVE=%d" % LIVE)
|
|
443
|
+
|
|
444
|
+
print("\n=== N202 session notes visibility: %d passed, %d failed (live=%d) ==="
|
|
445
|
+
% (PASS, FAIL, LIVE))
|
|
446
|
+
if FAILS:
|
|
447
|
+
print("失败项:%s" % ";".join(FAILS))
|
|
448
|
+
return 1 if FAIL else 0
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
if __name__ == "__main__":
|
|
452
|
+
_code = 0
|
|
453
|
+
try:
|
|
454
|
+
_code = main()
|
|
455
|
+
finally:
|
|
456
|
+
# 清洁纪律:先关实例(释放索引锁句柄),再清临时根;清不掉如实开口。
|
|
457
|
+
_close_all()
|
|
458
|
+
_dbg_ok = _cleanup(_TMP)
|
|
459
|
+
if not _dbg_ok:
|
|
460
|
+
print("WARN: 临时根残留未清(%s)——请人工清理" % _TMP)
|
|
461
|
+
sys.exit(_code)
|