@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,382 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 提案队列的密级/身份面(N201)
|
|
3
|
+
|
|
4
|
+
缺陷(修复前):海马体提案走一条与节点正文**口径不一致**的落盘/读出面:
|
|
5
|
+
|
|
6
|
+
· 写面(`md_cg/mdcos.py:1794-1803` `MdCGOS.propose`):`rec["content"]` 以
|
|
7
|
+
**明文**写进 `hippocampus/inbox.jsonl`,且 rec 顶层键**没有**
|
|
8
|
+
`sensitivity`(密级只藏在 `extra` 内,`payload_hash` 按明文算)——同源节点
|
|
9
|
+
正文「private 落盘即封套」(`MdCGSecure._seal_content`)在提案面整段缺失。
|
|
10
|
+
· 读面(`md_cg/mdcos.py:1946-1963` `review_list`):全函数体**零**
|
|
11
|
+
`_readable`、零身份判定,原样返回全部待审提案的正文 + actor/session 归属。
|
|
12
|
+
· 出口面(`md_cg/mcp_server.py:3067`):细粒度工具 `mdcg_review_list` 映射到
|
|
13
|
+
op `read`(`guest` 的 ops_allow 就含 read),而同库的规范出口
|
|
14
|
+
`cg(op=review, action=list)` 要 `require_op("review")` ⇒ **同一库两条出口
|
|
15
|
+
口径矛盾**:任一无令牌访客经细粒度工具即可读走 designer 以
|
|
16
|
+
`sensitivity='private'` 提交的候选正文明文与归属(DSH 插件强制 full 工具面,
|
|
17
|
+
`src/lib/mdcg_client.ts:121`,故该出口在插件部署下可达)。
|
|
18
|
+
|
|
19
|
+
修复(最小改动 · 与既有单点同口径):
|
|
20
|
+
① `MdCGOS.propose` 的 rec 顶层补 `sensitivity`(取自 kw/extra,与节点
|
|
21
|
+
frontmatter / 索引条目同形状,读侧判据免挖 extra);
|
|
22
|
+
② `MdCGSecure.review_list` 覆写:逐条按 `_readable`(clearance × sensitivity
|
|
23
|
+
∧ 会话绑定,设计者豁免)过滤,密级未知按最高档 fail-closed;legacy 提案的
|
|
24
|
+
密级从 `extra` 回退读取(存量不误伤)、封套内容仍按 `_open_content` 口径解封;
|
|
25
|
+
③ `mdcg_review_list` 的 op 要求对齐规范出口 ⇒ `"review"`(两出口同口径);
|
|
26
|
+
④ 次生面:`MdCGSecure.review_list` 的 op 闸另引出只读体检面
|
|
27
|
+
(`mdcg_health`,op=read 即可达)不再直连 `review_list` —— 新增
|
|
28
|
+
`_review_list_visible`(基类=全量、隔离层=按可见性收窄,无 op 闸),
|
|
29
|
+
health_os 的 `review_pending` 走它(否则 record/reflect/output/guest
|
|
30
|
+
的体检面会被打成 AccessDenied)。
|
|
31
|
+
|
|
32
|
+
**刻意不改(本批如实留档)**:`inbox.jsonl` 的 `content` 仍为明文落盘。
|
|
33
|
+
提案队列是**跨身份交接面**,而 DEK 按身份签发(`crypto.provision_dek
|
|
34
|
+
(root, kek, tenant, actor)`,实测跨身份解密抛 CryptoError)——若按提案者
|
|
35
|
+
密钥封套落盘,复核者(另一身份)将**读不开自己必须裁决的候选**,accept
|
|
36
|
+
还会把提案者密钥的密文原样落进节点(`_write_node` 见 `is_encrypted` 即跳过
|
|
37
|
+
再封套),裁成盲审 + 节点此后对本身份以外永久不可读。故本批的补偿控制是
|
|
38
|
+
「读面身份闸(两出口)+ 落盘密级留档(rec 顶层 sensitivity,为将来迁移
|
|
39
|
+
留字段)」;静态明文面属运维级信任边界(能读 root 文件系统者=运维权限),
|
|
40
|
+
如实标注为本轮残留,不冒充已修。
|
|
41
|
+
|
|
42
|
+
守卫(六组,全部临时库 + 哑主密钥 + 临时令牌库,**绝不触真实 ~/.mdcg**):
|
|
43
|
+
⓿ 隔离自证;① 写面:rec 顶层 sensitivity / extra 保形 / 明文留档;
|
|
44
|
+
② 读面:guest 两出口同拒 + 复核者按密级与会话过滤(内部档被滤 / 高密级异
|
|
45
|
+
会话仍滤 / 同会话可见 / 设计者全见 / 两出口 pid 集一致)+ 次生面(体检面
|
|
46
|
+
`mdcg_health` 不被 op 闸误伤、位审计数随可见性收窄)+ 阳性对照(设计者经
|
|
47
|
+
工具面 accept 成功、落盘节点正文为封套、裁决后出队);
|
|
48
|
+
③ 泄漏量化(guest 若被放行则正文与归属必命中,一并钉死);
|
|
49
|
+
④ 源断言 + live floor。
|
|
50
|
+
|
|
51
|
+
运行:python -X utf8 -m md_cg.test_n201_proposal_visibility
|
|
52
|
+
"""
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import atexit
|
|
56
|
+
import json
|
|
57
|
+
import os
|
|
58
|
+
import shutil
|
|
59
|
+
import sys
|
|
60
|
+
import tempfile
|
|
61
|
+
import time
|
|
62
|
+
|
|
63
|
+
# ── 隔离前置:必须在 import md_cg 子模块**之前**(tokens/theory 等在模块级把
|
|
64
|
+
# aux_root() 冻成常量)——否则会指到真实 ~/.mdcg 并在那里建密钥/令牌库。 ──
|
|
65
|
+
_TMP = tempfile.mkdtemp(prefix="n201_")
|
|
66
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
67
|
+
os.environ["MDCG_MASTER_KEY"] = "12" * 32 # 哑主密钥(绝不触真实密钥面)
|
|
68
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP, "tokens.json")
|
|
69
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
|
|
70
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
71
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
72
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_ACTOR", "MDCG_STATE_ROOT",
|
|
73
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES",
|
|
74
|
+
"PYTHONIOENCODING", "PYTHONUTF8"):
|
|
75
|
+
os.environ.pop(_k, None)
|
|
76
|
+
|
|
77
|
+
from . import crypto, mcp_server, nodefile, tokens # noqa: E402
|
|
78
|
+
from .datapath import aux_root # noqa: E402
|
|
79
|
+
from .fsutil import read_jsonl # noqa: E402
|
|
80
|
+
from .mdcos import MdCGSecure # noqa: E402
|
|
81
|
+
from .security import Principal # noqa: E402
|
|
82
|
+
|
|
83
|
+
atexit.register(lambda: _cleanup(_TMP)) # LIFO:晚于库层 _atexit_flush_all
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
87
|
+
except AttributeError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
ROOT = os.path.join(_TMP, "cg")
|
|
91
|
+
PASS = FAIL = LIVE = 0
|
|
92
|
+
LIVE_FLOOR = 12
|
|
93
|
+
FAILS = []
|
|
94
|
+
|
|
95
|
+
MARK = "N201-PROP-MARK-明文"
|
|
96
|
+
PRIV_BODY = "# 功能名:私密候选\n# 正文:%s(designer 提交,sensitivity=private)\n" % MARK
|
|
97
|
+
INT_BODY = "# 功能名:共享候选\n# 正文:N201-INBOX-共享档提案(record 提交,internal)\n"
|
|
98
|
+
S_DESIGN = "sess_n201_design"
|
|
99
|
+
S_ORCH = "sess_n201_orch"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check(name, cond, detail="", live=False):
|
|
103
|
+
global PASS, FAIL, LIVE
|
|
104
|
+
if live:
|
|
105
|
+
LIVE += 1
|
|
106
|
+
if cond:
|
|
107
|
+
PASS += 1
|
|
108
|
+
print(" [PASS] %s" % name)
|
|
109
|
+
else:
|
|
110
|
+
FAIL += 1
|
|
111
|
+
FAILS.append(name)
|
|
112
|
+
print(" [FAIL] %s · %s" % (name, detail))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _princ(role, clearance="internal", can_admin=False, session=None,
|
|
116
|
+
actor=None, layers=None):
|
|
117
|
+
spec = tokens.role_spec(role)
|
|
118
|
+
return Principal(tenant="default", actor=actor or ("n201-" + role),
|
|
119
|
+
clearance=clearance, can_write=bool(spec["can_write"]),
|
|
120
|
+
can_admin=can_admin, role=role, session=session,
|
|
121
|
+
layers_allow=(spec["layers_allow"] if layers is None
|
|
122
|
+
else layers),
|
|
123
|
+
ops_allow=spec["ops_allow"], auth_mode="test")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
_OPEN = []
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _secured(principal):
|
|
130
|
+
cg = MdCGSecure(ROOT, principal=principal)
|
|
131
|
+
_OPEN.append(cg)
|
|
132
|
+
return cg
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _close_all():
|
|
136
|
+
for cg in _OPEN:
|
|
137
|
+
try:
|
|
138
|
+
cg.close()
|
|
139
|
+
except Exception: # noqa: BLE001 —— 清理面不得再抛
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _cleanup(path) -> bool:
|
|
144
|
+
import gc
|
|
145
|
+
for _ in range(6):
|
|
146
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
147
|
+
if not os.path.exists(path):
|
|
148
|
+
return True
|
|
149
|
+
gc.collect()
|
|
150
|
+
time.sleep(0.25)
|
|
151
|
+
return not os.path.exists(path)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _raises(fn, *a, **kw):
|
|
155
|
+
"""执行并返回 (ok, 异常类名/空, 结果/异常文本)。"""
|
|
156
|
+
try:
|
|
157
|
+
return True, "", fn(*a, **kw)
|
|
158
|
+
except Exception as exc: # noqa: BLE001 —— 守卫要的是拒绝形态
|
|
159
|
+
return False, type(exc).__name__, str(exc)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _src_window(src, start, end):
|
|
163
|
+
i = src.find(start)
|
|
164
|
+
if i < 0:
|
|
165
|
+
return ""
|
|
166
|
+
j = src.find(end, i)
|
|
167
|
+
return src[i:] if j < 0 else src[i:j]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _read_src(rel):
|
|
171
|
+
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), rel)
|
|
172
|
+
with open(p, encoding="utf-8") as f:
|
|
173
|
+
return f.read()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _raw_rec(cg, pid):
|
|
177
|
+
"""原样读盘上 inbox 行(不经任何内存缓存)。"""
|
|
178
|
+
for r in read_jsonl(cg.inbox_log):
|
|
179
|
+
if r.get("pid") == pid:
|
|
180
|
+
return r
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _pids(res):
|
|
185
|
+
return sorted(str(r.get("pid")) for r in (res or {}).get("pending") or [])
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
global LIVE
|
|
190
|
+
# ---------- ⓿ 隔离自证 ----------
|
|
191
|
+
print("[0] 隔离自证")
|
|
192
|
+
_tok = tokens.token_file()
|
|
193
|
+
check("0a 默认令牌库与 aux 根都落在守卫临时目录(绝不触真实凭据)",
|
|
194
|
+
os.path.abspath(_tok).lower().startswith(os.path.abspath(_TMP).lower())
|
|
195
|
+
and os.path.abspath(aux_root()).lower().startswith(
|
|
196
|
+
os.path.abspath(_TMP).lower()),
|
|
197
|
+
"tok=%s aux=%s" % (_tok, aux_root()))
|
|
198
|
+
if not os.path.abspath(ROOT).lower().startswith(
|
|
199
|
+
os.path.abspath(_TMP).lower()):
|
|
200
|
+
print("FATAL: 隔离失败,中止(绝不带病跑真实库)")
|
|
201
|
+
return 2
|
|
202
|
+
|
|
203
|
+
# ---------- 夹具:designer 提 private、record 提 internal ----------
|
|
204
|
+
print("\n[夹具] designer 提 private 候选 / record 提 internal 候选")
|
|
205
|
+
design = _secured(_princ("designer", clearance="secret", can_admin=True,
|
|
206
|
+
session=S_DESIGN, actor="n201-design"))
|
|
207
|
+
p_priv = design.propose("n201_priv", PRIV_BODY, layer="knowledge",
|
|
208
|
+
sensitivity="private", tags=["n201"])
|
|
209
|
+
rec = _secured(_princ("record", session="sess_n201_rec", actor="n201-rec"))
|
|
210
|
+
p_int = rec.propose("n201_int", INT_BODY, layer="knowledge", tags=["n201"])
|
|
211
|
+
check("0b 夹具:两条提案入队(pid 前缀 prop_)",
|
|
212
|
+
str(p_priv).startswith("prop_") and str(p_int).startswith("prop_"),
|
|
213
|
+
"priv=%r int=%r" % (p_priv, p_int))
|
|
214
|
+
|
|
215
|
+
# ---------- ① 写面:rec 顶层密级 + extra 保形 ----------
|
|
216
|
+
print("\n【①】写面:提案落盘的密级口径")
|
|
217
|
+
r_priv = _raw_rec(design, p_priv) or {}
|
|
218
|
+
r_int = _raw_rec(design, p_int) or {}
|
|
219
|
+
check("1a rec 顶层带 sensitivity(与节点 frontmatter/索引条目同形状)",
|
|
220
|
+
r_priv.get("sensitivity") == "private"
|
|
221
|
+
and r_int.get("sensitivity") == "internal",
|
|
222
|
+
"顶层 sensitivity=%r / %r(修前:仅 extra 内有)"
|
|
223
|
+
% (r_priv.get("sensitivity"), r_int.get("sensitivity")), live=True)
|
|
224
|
+
check("1b extra.sensitivity 保形(存量读取方零变化)",
|
|
225
|
+
(r_priv.get("extra") or {}).get("sensitivity") == "private",
|
|
226
|
+
str((r_priv.get("extra") or {}).get("sensitivity")))
|
|
227
|
+
check("1c 如实留档:正文仍为明文落在 inbox.jsonl(本批不封套,见报告)",
|
|
228
|
+
MARK in str(r_priv.get("content") or ""),
|
|
229
|
+
"content=%r" % (str(r_priv.get("content") or "")[:60]))
|
|
230
|
+
|
|
231
|
+
# ---------- ② 读面:细粒度工具 vs 规范出口 ----------
|
|
232
|
+
print("\n【②】读面:guest 两出口同拒 / 复核者按密级与会话过滤")
|
|
233
|
+
guest = _secured(_princ("guest", clearance="internal", session="sess_n201_g"))
|
|
234
|
+
ok_t, exc_t, res_t = _raises(mcp_server._dispatch, guest,
|
|
235
|
+
"mdcg_review_list", {})
|
|
236
|
+
_leak = json.dumps(res_t, ensure_ascii=False) if ok_t else ""
|
|
237
|
+
check("2a guest 经细粒度工具 mdcg_review_list 被 AccessDenied"
|
|
238
|
+
"(正文与 actor/session 归属均不返回)",
|
|
239
|
+
(not ok_t) and exc_t == "AccessDenied"
|
|
240
|
+
and MARK not in _leak and S_DESIGN not in _leak,
|
|
241
|
+
"exc=%r leaked_mark=%r" % (exc_t, MARK in _leak), live=True)
|
|
242
|
+
ok_k, exc_k, _res_k = _raises(mcp_server._dispatch, guest, "cg",
|
|
243
|
+
{"op": "review", "action": "list"})
|
|
244
|
+
check("2b 规范出口 cg(op=review, action=list) 对同身份同样 AccessDenied"
|
|
245
|
+
"(两条出口口径一致)",
|
|
246
|
+
(not ok_k) and exc_k == "AccessDenied", "exc=%r" % exc_k, live=True)
|
|
247
|
+
orch = _secured(_princ("orchestr", clearance="internal",
|
|
248
|
+
session=S_ORCH, actor="n201-orch"))
|
|
249
|
+
ok_o, exc_o, res_o = _raises(mcp_server._dispatch, orch,
|
|
250
|
+
"mdcg_review_list", {})
|
|
251
|
+
check("2c 复核者(orchestr/internal)看不到 private 候选、共享档仍可见",
|
|
252
|
+
ok_o and p_priv not in _pids(res_o) and p_int in _pids(res_o),
|
|
253
|
+
"pids=%r exc=%r" % (_pids(res_o) if ok_o else None, exc_o), live=True)
|
|
254
|
+
orch_hi = _secured(_princ("orchestr", clearance="private",
|
|
255
|
+
session=S_ORCH, actor="n201-orch-hi"))
|
|
256
|
+
ok_h, _e_h, res_h = _raises(mcp_server._dispatch, orch_hi,
|
|
257
|
+
"mdcg_review_list", {})
|
|
258
|
+
check("2d 高密级但异会话的复核者:会话绑定档仍不可见(session 是隔离维度)",
|
|
259
|
+
ok_h and p_priv not in _pids(res_h) and p_int in _pids(res_h),
|
|
260
|
+
"pids=%r" % (_pids(res_h) if ok_h else None), live=True)
|
|
261
|
+
orch_same = _secured(_princ("orchestr", clearance="private",
|
|
262
|
+
session=S_DESIGN, actor="n201-orch-same"))
|
|
263
|
+
ok_s, _e_s, res_s = _raises(mcp_server._dispatch, orch_same,
|
|
264
|
+
"mdcg_review_list", {})
|
|
265
|
+
check("2e 阳性对照:同会话且密级达标的复核者可见该 private 候选(未过度封禁)",
|
|
266
|
+
ok_s and p_priv in _pids(res_s),
|
|
267
|
+
"pids=%r" % (_pids(res_s) if ok_s else None), live=True)
|
|
268
|
+
ok_d, _e_d, res_d = _raises(mcp_server._dispatch, design,
|
|
269
|
+
"mdcg_review_list", {})
|
|
270
|
+
check("2f 设计者(can_admin)两条都在(管理位豁免,队列不被掏空)",
|
|
271
|
+
ok_d and p_priv in _pids(res_d) and p_int in _pids(res_d),
|
|
272
|
+
"pids=%r" % (_pids(res_d) if ok_d else None), live=True)
|
|
273
|
+
_k_o = _raises(mcp_server._dispatch, orch, "cg",
|
|
274
|
+
{"op": "review", "action": "list"})
|
|
275
|
+
check("2g 工具面与规范出口的待审 pid 集逐字一致(同一过滤单点)",
|
|
276
|
+
ok_o and _k_o[0] and _pids(res_o) == _pids(_k_o[2]),
|
|
277
|
+
"tool=%r kernel=%r" % (_pids(res_o) if ok_o else None,
|
|
278
|
+
_pids(_k_o[2]) if _k_o[0] else _k_o[1]),
|
|
279
|
+
live=True)
|
|
280
|
+
|
|
281
|
+
# 次生面:只读体检面(mdcg_health,op=read 即可达)——本轮 op 闸只该管
|
|
282
|
+
# 「取正文」面,不得把体检面打成 AccessDenied;计数面须随可见性收窄
|
|
283
|
+
# (否则待审计数成了「暗中还有几条不可见提案」的越权元数据——低权身份
|
|
284
|
+
# 可由 计数−可见 反推隐藏提案的存在与条数)。
|
|
285
|
+
_h = _raises(mcp_server._dispatch, orch, "mdcg_health", {})
|
|
286
|
+
_hp = ((_h[2] or {}).get("os") or {}).get("review_pending")
|
|
287
|
+
_vis_n = len(_pids(res_o)) if ok_o else None
|
|
288
|
+
_all_n = len(_pids(res_d)) if ok_d else None
|
|
289
|
+
check("2h 次生面:体检面 mdcg_health 对低权身份可用不抛,且位审计数"
|
|
290
|
+
"= 该身份可见待审数(可见集 ⊊ 全体:不越权暴露不可见提案的存在性)",
|
|
291
|
+
_h[0] and _hp is not None and _vis_n is not None
|
|
292
|
+
and _all_n is not None and _vis_n < _all_n and _hp == _vis_n,
|
|
293
|
+
"exc=%r health_pending=%r 可见=%r 全体=%r"
|
|
294
|
+
% (_h[1], _hp, _vis_n, _all_n), live=True)
|
|
295
|
+
|
|
296
|
+
# 阳性对照:合规裁决路径在修复后仍走得通(本修复只收口越权读面,不封裁决面)
|
|
297
|
+
ok_dd, exc_dd, res_dd = _raises(mcp_server._dispatch, design,
|
|
298
|
+
"mdcg_review_decide",
|
|
299
|
+
{"pid": p_priv, "decision": "accept",
|
|
300
|
+
"reason": "N201 守卫:合规裁决"})
|
|
301
|
+
check("2i 设计者经工具面 accept 私密候选成功(未过度收口)",
|
|
302
|
+
ok_dd and bool((res_dd or {}).get("ok"))
|
|
303
|
+
and (res_dd or {}).get("node_id") == "n201_priv",
|
|
304
|
+
"exc=%r res=%r" % (exc_dd, res_dd), live=True)
|
|
305
|
+
_e_np = (design.index.get("nodes") or {}).get("n201_priv") or {}
|
|
306
|
+
_disk = ""
|
|
307
|
+
if _e_np.get("path"):
|
|
308
|
+
try:
|
|
309
|
+
with open(os.path.join(ROOT, _e_np["path"]), encoding="utf-8") as f:
|
|
310
|
+
_disk = f.read()
|
|
311
|
+
except OSError as exc:
|
|
312
|
+
_disk = "<读盘失败 %s>" % exc
|
|
313
|
+
_disk_fm, _disk_body = {}, ""
|
|
314
|
+
if _disk:
|
|
315
|
+
try:
|
|
316
|
+
_disk_fm, _disk_body = nodefile.loads(_disk)
|
|
317
|
+
_disk_fm = _disk_fm or {}
|
|
318
|
+
except Exception as exc: # noqa: BLE001 —— 诊断面
|
|
319
|
+
_disk_fm = {"<解析失败>": str(exc)}
|
|
320
|
+
check("2j accept 落盘的 private 节点正文在盘上为封套"
|
|
321
|
+
"(同源「private 落盘即封套」口径,提案面不成为绕过口)",
|
|
322
|
+
bool(_disk) and crypto.is_encrypted(_disk_body) and MARK not in _disk,
|
|
323
|
+
"path=%r fm.sensitivity=%r sealed=%r 明文命中=%r body=%r"
|
|
324
|
+
% (_e_np.get("path"), _disk_fm.get("sensitivity"),
|
|
325
|
+
crypto.is_encrypted(_disk_body), MARK in _disk,
|
|
326
|
+
(_disk_body or "")[:90]), live=True)
|
|
327
|
+
ok_l2, _e_l2, res_l2 = _raises(mcp_server._dispatch, design,
|
|
328
|
+
"mdcg_review_list", {})
|
|
329
|
+
check("2k 裁决后该 pid 出队(工具面队列与规范出口同步收口)",
|
|
330
|
+
ok_l2 and p_priv not in _pids(res_l2),
|
|
331
|
+
"pids=%r" % (_pids(res_l2) if ok_l2 else None), live=True)
|
|
332
|
+
|
|
333
|
+
# ---------- ③ 泄漏量化(红态证据链) ----------
|
|
334
|
+
print("\n【③】泄漏量化:若 guest 被放行,正文/归属必命中")
|
|
335
|
+
_rows = res_t.get("pending") if ok_t else []
|
|
336
|
+
_hit_body = [r.get("pid") for r in _rows
|
|
337
|
+
if MARK in str(r.get("content") or "")]
|
|
338
|
+
_hit_attr = [r.get("pid") for r in _rows
|
|
339
|
+
if r.get("actor") == "n201-design"
|
|
340
|
+
and r.get("session") == S_DESIGN]
|
|
341
|
+
check("3a 修前形态判定:guest 一旦拿到结果,私密正文与归属都在其中",
|
|
342
|
+
(not ok_t) or (bool(_hit_body) and bool(_hit_attr)),
|
|
343
|
+
"body=%r attr=%r" % (_hit_body, _hit_attr), live=True)
|
|
344
|
+
|
|
345
|
+
# ---------- ④ 源断言 + floor ----------
|
|
346
|
+
print("\n【④】源断言(三处接线在位)")
|
|
347
|
+
_s_mdcos = _read_src("mdcos.py")
|
|
348
|
+
_prop = _src_window(_s_mdcos, "def propose(self, node_id", "def _pid_status")
|
|
349
|
+
check("4a propose 的 rec 顶层落 sensitivity(读侧判据免挖 extra)",
|
|
350
|
+
'"sensitivity": kw.get("sensitivity")' in _prop, _prop[-200:])
|
|
351
|
+
_rl = _src_window(_s_mdcos, "def review_list",
|
|
352
|
+
"# 生效条件:先 principal.require_op(\"review\")(语义修正 2026-09-22")
|
|
353
|
+
check("4b MdCGSecure.review_list 覆写 = op 闸 + _readable 过滤(密级 × 会话)",
|
|
354
|
+
"require_op(\"review\")" in _rl and "_readable" in _rl
|
|
355
|
+
and "sensitivity" in _rl, _rl[-260:])
|
|
356
|
+
_hsrc = _src_window(_s_mdcos, "def health_os(self):",
|
|
357
|
+
"def _log_scale")
|
|
358
|
+
check("4e 体检面的待审计数走可见性计数面(不被取正文面的 op 闸误伤)",
|
|
359
|
+
"_review_list_visible" in _hsrc, _hsrc[-160:])
|
|
360
|
+
_s_mcp = _read_src("mcp_server.py")
|
|
361
|
+
check("4c 细粒度工具 mdcg_review_list 的 op 要求对齐规范出口 = review",
|
|
362
|
+
'"mdcg_review_list": "review"' in _s_mcp,
|
|
363
|
+
_src_window(_s_mcp, '"mdcg_propose": "write"',
|
|
364
|
+
'"mdcg_review_decide"')[-160:])
|
|
365
|
+
print(" [INFO] live 断言数 = %d(floor=%d)" % (LIVE, LIVE_FLOOR))
|
|
366
|
+
check("4d live 断言数不低于 floor(防空转绿)", LIVE >= LIVE_FLOOR,
|
|
367
|
+
"live=%d" % LIVE)
|
|
368
|
+
|
|
369
|
+
print("\n" + "=" * 64)
|
|
370
|
+
print("PASS=%d FAIL=%d LIVE=%d" % (PASS, FAIL, LIVE))
|
|
371
|
+
if FAILS:
|
|
372
|
+
print("失败项:" + ";".join(FAILS))
|
|
373
|
+
return 0 if FAIL == 0 else 1
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
if __name__ == "__main__":
|
|
377
|
+
try:
|
|
378
|
+
rc = main()
|
|
379
|
+
finally:
|
|
380
|
+
_close_all()
|
|
381
|
+
time.sleep(0.05)
|
|
382
|
+
sys.exit(rc)
|