@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,171 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""真源索引契约层守卫(P0):通用层覆盖任意文件 / 细化层仅可读文本 / 伴随文本挂通用层 / 回读逐字节。
|
|
3
|
+
|
|
4
|
+
判据姿态(P0 两次踩坑的产物,勿简化回「猜粒度」):
|
|
5
|
+
· **不猜提取器粒度**:细化层单位数与 `docindex.extract` 条数**平权**(units == extract + 1),
|
|
6
|
+
H1 节合并、过短节并入等语义归提取器,本层只保证「它切几节我就几条」;
|
|
7
|
+
· **哈希单一实现**:行区间哈希委托 `codeindex.region_hash`(sha1 前 12 位);本模块自写第二份
|
|
8
|
+
的行为已被 5b/5c 抓出并删除——两处算法会让漂移检测永远 hash_match=True;
|
|
9
|
+
· **伴随文本挂通用层**:媒体不进可读文本分支,挂错层等于废掉多模态语义通道(6b 抓出)。
|
|
10
|
+
|
|
11
|
+
实验纪律:全程 `tempfile` 临时目录 + 合成件(含**假魔数**的合成二进制,绝不用真实媒体/私有数据);
|
|
12
|
+
不写任何库面、不碰在役库、不出网。运行:退出码 0 = 全绿。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import io
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
23
|
+
|
|
24
|
+
from md_cg import codeindex # noqa: E402
|
|
25
|
+
from md_cg import docindex # noqa: E402
|
|
26
|
+
from md_cg import srcindex as S # noqa: E402
|
|
27
|
+
|
|
28
|
+
PASS = FAIL = 0
|
|
29
|
+
FAILS = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ck(cid: str, ok: bool, detail: str = "") -> None:
|
|
33
|
+
global PASS, FAIL
|
|
34
|
+
if ok:
|
|
35
|
+
PASS += 1
|
|
36
|
+
else:
|
|
37
|
+
FAIL += 1
|
|
38
|
+
FAILS.append("%s %s" % (cid, detail))
|
|
39
|
+
print(" %s %s%s" % ("OK " if ok else "FAIL", cid, "" if ok else " " + detail))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def w(path: str, data) -> str:
|
|
43
|
+
if isinstance(data, bytes):
|
|
44
|
+
with open(path, "wb") as f:
|
|
45
|
+
f.write(data)
|
|
46
|
+
else:
|
|
47
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
48
|
+
f.write(data)
|
|
49
|
+
return path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
DOC = "\n".join([
|
|
53
|
+
"# 合成文档标题", "", "这是一段引言。", "",
|
|
54
|
+
"## 小节一", "", "小节一的正文,足够长以通过提取器的最小长度判断。", "",
|
|
55
|
+
"## 小节二", "", "小节二的正文,同样足够长,并带一行说明。", "",
|
|
56
|
+
"## 小节三", "", "小节三的正文。",
|
|
57
|
+
])
|
|
58
|
+
CCG = "\n".join([
|
|
59
|
+
"# 功能名:合成样例", "# 生效条件:仅用于 P0 契约层守卫;合成件、非真实数据",
|
|
60
|
+
"## 小节一", "正文一",
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main() -> int:
|
|
65
|
+
tmp = tempfile.mkdtemp(prefix="p0_srcindex_")
|
|
66
|
+
try:
|
|
67
|
+
md = w(os.path.join(tmp, "说明.md"), DOC)
|
|
68
|
+
ccg = w(os.path.join(tmp, "带条件.md"), CCG)
|
|
69
|
+
png = w(os.path.join(tmp, "pic.png"), b"\x89PNG\r\n\x1a\n" + b"\x00\x01\x02" * 40)
|
|
70
|
+
w(os.path.join(tmp, "pic.md"), "# pic 简介\n\n这是伴随文本(sidecar)")
|
|
71
|
+
mp4 = w(os.path.join(tmp, "clip.mp4"), b"\x00\x00\x00\x18ftypmp42" + b"\x00" * 64)
|
|
72
|
+
raw = w(os.path.join(tmp, "blob.dat"), bytes(range(256)) * 4)
|
|
73
|
+
weird = w(os.path.join(tmp, "t.txt"), b"abc\x00def") # NUL ⇒ 不可读
|
|
74
|
+
fake = w(os.path.join(tmp, "disguised.txt"), b"\x89PNG\r\n\x1a\n" + b"\x00" * 16)
|
|
75
|
+
empty = w(os.path.join(tmp, "empty.md"), "")
|
|
76
|
+
|
|
77
|
+
# ① 通用层:任意文件都有 ≥1 条 whole 单位(含空文件/不可读二进制)
|
|
78
|
+
for label, p in (("md", md), ("png", png), ("mp4", mp4), ("raw", raw),
|
|
79
|
+
("nul", weird), ("empty", empty)):
|
|
80
|
+
us = S.units(p)
|
|
81
|
+
ck("1a[%s] 通用层恒在" % label,
|
|
82
|
+
bool(us) and us[0]["span"]["unit"] == "whole" and bool(us[0]["span_hash"]),
|
|
83
|
+
"units=%d" % len(us))
|
|
84
|
+
|
|
85
|
+
# ② 细化层:与 docindex.extract 平权(不猜粒度)
|
|
86
|
+
for label, p in (("md", md), ("ccg", ccg)):
|
|
87
|
+
text = io.open(p, encoding="utf-8").read()
|
|
88
|
+
want = len(docindex.extract(text, path=p)) + 1
|
|
89
|
+
got = len(S.units(p))
|
|
90
|
+
ck("2a[%s] 单位数 == extract+1" % label, got == want, "units=%d want=%d" % (got, want))
|
|
91
|
+
ck("2b[png] 无细化层", all(u["span"]["unit"] == "whole" for u in S.units(png)))
|
|
92
|
+
ck("2c[NUL 文本] 判不可读、不细化",
|
|
93
|
+
all(u["span"]["unit"] == "whole" for u in S.units(weird)))
|
|
94
|
+
ck("2d[空文件] 不细化且不抛", len(S.units(empty)) == 1)
|
|
95
|
+
|
|
96
|
+
# ③ 类型判据:魔数优先于扩展名;未知类型不拒
|
|
97
|
+
ck("3a[魔数优先] .txt 内容为 PNG ⇒ image/png",
|
|
98
|
+
S.sniff_type(fake) == "image/png", S.sniff_type(fake))
|
|
99
|
+
ck("3b[扩展名兜底] .md ⇒ text/markdown", S.sniff_type(md) == "text/markdown")
|
|
100
|
+
ck("3c[未知不拒] .dat ⇒ octet-stream",
|
|
101
|
+
S.sniff_type(raw) == "application/octet-stream", S.sniff_type(raw))
|
|
102
|
+
|
|
103
|
+
# ④ 哈希判别力:改 1 字节 ⇒ file_hash 变;旧单位回读 hash_match=False
|
|
104
|
+
u0 = S.units(md)[0]
|
|
105
|
+
h0 = u0["src"]["file_hash"]
|
|
106
|
+
w(md, DOC.replace("这是一段引言。", "这是一段引言!"))
|
|
107
|
+
u1 = S.units(md)[0]
|
|
108
|
+
ck("4a[1 字节改判] file_hash 变", h0 != u1["src"]["file_hash"])
|
|
109
|
+
ck("4b[陈旧检出] 旧单位回读 hash_match=False",
|
|
110
|
+
S.read_unit(u0).get("hash_match") is False)
|
|
111
|
+
ck("4c[新鲜一致] 新单位回读 hash_match=True",
|
|
112
|
+
S.read_unit(u1).get("hash_match") is True)
|
|
113
|
+
|
|
114
|
+
# ⑤ 行区间回读逐字节 + 与 codeindex.region_hash 单一实现
|
|
115
|
+
lu = [u for u in S.units(md) if u["span"]["unit"] == "line"][0]
|
|
116
|
+
got = S.read_unit(lu)
|
|
117
|
+
lines = io.open(md, encoding="utf-8").read().split("\n")
|
|
118
|
+
want_text = "\n".join(lines[lu["span"]["start"] - 1:lu["span"]["end"]])
|
|
119
|
+
ck("5a[区间逐字节]", got.get("text") == want_text, repr(got.get("text"))[:60])
|
|
120
|
+
ck("5b[区间哈希一致]", got.get("hash_match") is True)
|
|
121
|
+
ck("5c[哈希单一实现] 委托 codeindex.region_hash",
|
|
122
|
+
codeindex.region_hash(lines, lu["span"]["start"], lu["span"]["end"])
|
|
123
|
+
== S.region_hash("\n".join(lines), lu["span"]["start"], lu["span"]["end"])
|
|
124
|
+
== got["hash"])
|
|
125
|
+
|
|
126
|
+
# ⑥ 伴随文本挂**通用层**(媒体条目的语义通道)
|
|
127
|
+
comps = S.companions(png)
|
|
128
|
+
u_png = S.units(png)[0]
|
|
129
|
+
ck("6a[sidecar 发现]", any(c["kind"] == "sidecar" for c in comps),
|
|
130
|
+
str([c["name"] for c in comps]))
|
|
131
|
+
ck("6b[挂进通用层] media 条目 text_view 含伴随文本且带 companions",
|
|
132
|
+
"伴随文本" in u_png["text_view"] and bool(u_png.get("companions")))
|
|
133
|
+
|
|
134
|
+
# ⑦ 未实现 locator 必须显式不静默
|
|
135
|
+
bad = S.read_unit({"src": {"path": md}, "span": {"unit": "time", "t0": 0, "t1": 1}})
|
|
136
|
+
ck("7a[time 显式不支持]", bad.get("ok") is False and "time" in bad.get("error", ""),
|
|
137
|
+
str(bad)[:80])
|
|
138
|
+
|
|
139
|
+
# ⑧ CCG 条件面进入细化层(且与该文件的 CCG 行相关)
|
|
140
|
+
ccg_units = S.units(ccg)
|
|
141
|
+
ck("8a[条件面非空]", any(u.get("condition_space") for u in ccg_units),
|
|
142
|
+
str([bool(u.get("condition_space")) for u in ccg_units]))
|
|
143
|
+
ck("8b[CCG 行落在细化层]",
|
|
144
|
+
any("生效条件" in (u.get("text_view") or "") or
|
|
145
|
+
"生效条件" in str(u.get("condition_space"))
|
|
146
|
+
for u in ccg_units if u["span"]["unit"] == "line"),
|
|
147
|
+
str([u.get("text_view") for u in ccg_units if u["span"]["unit"] == "line"])[:90])
|
|
148
|
+
|
|
149
|
+
# ⑨ 能力自陈与实现一致(契约漂移守卫)
|
|
150
|
+
cap = S.capability()
|
|
151
|
+
ck("9a[units 自陈]", tuple(cap["units"]) == S.UNITS == ("whole", "line"))
|
|
152
|
+
ck("9b[未实现项自陈]", set(cap["not_implemented"]) == {"time", "region", "byte"})
|
|
153
|
+
ck("9c[零依赖/不写状态]", cap["deps"] == [] and cap["writes_state"] is False)
|
|
154
|
+
|
|
155
|
+
# ⑩ 不存在路径:fail-closed 抛,而不是静默造条目
|
|
156
|
+
try:
|
|
157
|
+
S.units(os.path.join(tmp, "不存在.bin"))
|
|
158
|
+
ck("10a[不存在 fail-closed]", False, "未抛")
|
|
159
|
+
except OSError:
|
|
160
|
+
ck("10a[不存在 fail-closed]", True)
|
|
161
|
+
finally:
|
|
162
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
163
|
+
|
|
164
|
+
print("\n结果:PASS %d / FAIL %d" % (PASS, FAIL))
|
|
165
|
+
if FAILS:
|
|
166
|
+
print("失败项:" + ";".join(FAILS))
|
|
167
|
+
return 0 if FAIL == 0 else 1
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
sys.exit(main())
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""租户绑定 env
|
|
2
|
+
"""租户绑定 env 覆盖守卫(v8 N62 止血告警 → 本轮 N62 第 5 轮 fail-closed)。
|
|
3
3
|
|
|
4
4
|
攻击面:控制 MCP 进程 env 的部署面(宿主配置 / 启动器 / 同机多会话)——
|
|
5
5
|
tenantA 签发的 designer(secret, can_admin) 令牌在 MDCG_TENANT=tenantB 下经
|
|
6
|
-
mcp_server._build_principal → verify_token(tok, tenant=env)
|
|
7
|
-
|
|
6
|
+
mcp_server._build_principal → verify_token(tok, tenant=env) 以 tenantB 身份
|
|
7
|
+
运行(tenant=tenant or rec.get("tenant") 形参优先)。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
契约演进(N62 五轮留档):
|
|
10
|
+
v8/v9/第15轮:仅 stderr 告警零闸变(止血口径——先开口,强校验 deferred);
|
|
11
|
+
本轮(2026-09-27 第 5 次,medium 维持):告警改 fail-closed 拒绝
|
|
12
|
+
(tokens.verify_token 抛 TokenError,消息含两侧租户与校正指引),并接线
|
|
13
|
+
登记租户 clearance_cap 夹紧(MDCG_TENANT_REGISTRY,与 mcp_server.
|
|
14
|
+
_resolve_root 同源;未登记租户零闸变)。
|
|
13
15
|
|
|
14
16
|
运行:python -m md_cg.test_tenant_env_override_warn
|
|
15
17
|
"""
|
|
@@ -26,7 +28,7 @@ from . import tokens
|
|
|
26
28
|
|
|
27
29
|
PASS = FAIL = 0
|
|
28
30
|
FAILS = []
|
|
29
|
-
MARK = "租户绑定被入参覆盖" #
|
|
31
|
+
MARK = "租户绑定被入参覆盖" # fail-closed 拒绝消息稳定锚点
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def check(name, cond, detail=""):
|
|
@@ -41,82 +43,89 @@ def check(name, cond, detail=""):
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
def _verify_capture(token, tenant, path):
|
|
44
|
-
"""verify_token
|
|
46
|
+
"""verify_token 捕获异常与 stderr,返回 (principal|None, stderr_text, exc|None)。"""
|
|
45
47
|
buf = io.StringIO()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
p = exc = None
|
|
49
|
+
try:
|
|
50
|
+
with contextlib.redirect_stderr(buf):
|
|
51
|
+
p = tokens.verify_token(token, tenant=tenant, path=path)
|
|
52
|
+
except tokens.TokenError as e:
|
|
53
|
+
exc = e
|
|
54
|
+
return p, buf.getvalue(), exc
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
def main():
|
|
52
58
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
53
59
|
root = tempfile.mkdtemp(prefix="mdcg_tenant_warn_")
|
|
54
60
|
tf = os.path.join(root, "_tokens.json")
|
|
61
|
+
# 登记表钉在临时不存在路径:守卫全程不触真实 ~/.mdcg/_tenants.json,
|
|
62
|
+
# 且「未登记 → 零夹紧」确定性(冲突拒绝发生在登记表访问之前)。
|
|
63
|
+
reg_path = os.path.join(root, "_tenants_absent.json")
|
|
64
|
+
old_reg = os.environ.get("MDCG_TENANT_REGISTRY")
|
|
65
|
+
os.environ["MDCG_TENANT_REGISTRY"] = reg_path
|
|
55
66
|
try:
|
|
56
|
-
# ---------------- ①
|
|
57
|
-
print("\n[1] 攻击复现:tenantA 令牌被 tenantB
|
|
67
|
+
# ---------------- ① 攻击复现(单元级,fail-closed) ----------------
|
|
68
|
+
print("\n[1] 攻击复现:tenantA 令牌被 tenantB 形参覆盖 → 拒绝")
|
|
58
69
|
d = tokens.issue("designer", actor="dummy-designer", tenant="tenantA",
|
|
59
70
|
path=tf)
|
|
60
|
-
p, err = _verify_capture(d["token"], "tenantB", tf)
|
|
61
|
-
check("
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
check("
|
|
71
|
-
p.role == "designer" and p.can_admin is True,
|
|
72
|
-
f"role={p.role} admin={p.can_admin}")
|
|
71
|
+
p, err, exc = _verify_capture(d["token"], "tenantB", tf)
|
|
72
|
+
check("冲突覆盖被拒(TokenError 且含锚点)",
|
|
73
|
+
p is None and exc is not None and MARK in str(exc),
|
|
74
|
+
f"p={p} exc={type(exc).__name__}: {str(exc)[:80]}")
|
|
75
|
+
check("拒绝消息含两侧租户与校正指引",
|
|
76
|
+
exc is not None and "tenantA" in str(exc) and "tenantB" in str(exc)
|
|
77
|
+
and "MDCG_TENANT" in str(exc), str(exc)[:100])
|
|
78
|
+
|
|
79
|
+
# ---------------- ② fail-closed(覆盖零放行) ----------------
|
|
80
|
+
print("\n[2] fail-closed(冲突路径零 Principal 放行)")
|
|
81
|
+
check("冲突路径不返回 Principal", p is None, f"p={p}")
|
|
73
82
|
|
|
74
83
|
# ---------------- ③ 无冲突路径零误伤 ----------------
|
|
75
84
|
print("\n[3] 无冲突路径零误伤")
|
|
76
|
-
p0, err0 = _verify_capture(d["token"], None, tf)
|
|
77
|
-
check("tenant=None
|
|
78
|
-
MARK not in err0 and p0.tenant == "tenantA",
|
|
79
|
-
f"tenant={p0.tenant}")
|
|
80
|
-
p1, err1 = _verify_capture(d["token"], "", tf)
|
|
81
|
-
check("tenant=''(env
|
|
82
|
-
MARK not in err1 and p1.tenant == "tenantA",
|
|
83
|
-
f"tenant={p1.tenant}")
|
|
84
|
-
p2, err2 = _verify_capture(d["token"], "tenantA", tf)
|
|
85
|
-
check("tenant
|
|
86
|
-
|
|
85
|
+
p0, err0, exc0 = _verify_capture(d["token"], None, tf)
|
|
86
|
+
check("tenant=None:零异常且回退令牌租户",
|
|
87
|
+
exc0 is None and MARK not in err0 and p0.tenant == "tenantA",
|
|
88
|
+
f"tenant={p0.tenant} err={exc0}")
|
|
89
|
+
p1, err1, exc1 = _verify_capture(d["token"], "", tf)
|
|
90
|
+
check("tenant=''(env 未设/空串):零异常且回退令牌租户",
|
|
91
|
+
exc1 is None and MARK not in err1 and p1.tenant == "tenantA",
|
|
92
|
+
f"tenant={p1.tenant} err={exc1}")
|
|
93
|
+
p2, err2, exc2 = _verify_capture(d["token"], "tenantA", tf)
|
|
94
|
+
check("tenant 同值:零异常零告警",
|
|
95
|
+
exc2 is None and MARK not in err2 and p2.tenant == "tenantA",
|
|
96
|
+
f"tenant={p2.tenant} err={exc2}")
|
|
87
97
|
|
|
88
98
|
# ---------------- ④ MCP 进程级入口(_build_principal 直调先例 b26) ----------------
|
|
89
99
|
print("\n[4] MCP 进程级:MDCG_TOKEN(tenantA) + MDCG_TENANT=tenantB")
|
|
90
100
|
from .mcp_server import _build_principal
|
|
91
101
|
saved = {k: os.environ.get(k) for k in
|
|
92
102
|
("MDCG_TOKEN", "MDCG_TENANT", "MDCG_TOKEN_FILE", "MDCG_ROOT",
|
|
93
|
-
"
|
|
103
|
+
"MDCG_TENANT_REGISTRY", "MDCG_LEGACY_ENV_AUTH",
|
|
104
|
+
"MDCG_LEGACY_ENV_ADMIN", "MDCG_CAN_ADMIN", "MDCG_CAN_WRITE",
|
|
94
105
|
"MDCG_CLEARANCE", "MDCG_ACTOR")}
|
|
95
106
|
try:
|
|
96
107
|
os.environ["MDCG_TOKEN"] = d["token"]
|
|
97
108
|
os.environ["MDCG_TOKEN_FILE"] = tf
|
|
98
109
|
os.environ["MDCG_ROOT"] = os.path.join(root, "mcp")
|
|
110
|
+
os.environ["MDCG_TENANT_REGISTRY"] = reg_path
|
|
99
111
|
os.environ["MDCG_TENANT"] = "tenantB"
|
|
100
|
-
for k in ("MDCG_LEGACY_ENV_AUTH", "
|
|
101
|
-
"
|
|
112
|
+
for k in ("MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
113
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_CLEARANCE",
|
|
114
|
+
"MDCG_ACTOR"):
|
|
102
115
|
os.environ.pop(k, None)
|
|
103
116
|
buf = io.StringIO()
|
|
104
117
|
with contextlib.redirect_stderr(buf):
|
|
105
118
|
mp, merr = _build_principal()
|
|
106
119
|
mout = buf.getvalue()
|
|
107
|
-
check("MCP
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
f"tenant={getattr(mp, 'tenant', None)}")
|
|
112
|
-
check("MCP 入口 stderr 含覆盖告警", MARK in mout,
|
|
113
|
-
(mout.strip().splitlines() or ["(零 stderr)"])[-1][:90])
|
|
114
|
-
# 无冲突对照:MDCG_TENANT 与令牌租户一致 → 零告警
|
|
120
|
+
check("MCP 入口覆盖被拒(err 含锚点,principal=None)",
|
|
121
|
+
mp is None and merr is not None and MARK in merr,
|
|
122
|
+
f"err={str(merr)[:90]}")
|
|
123
|
+
# 无冲突对照:MDCG_TENANT 与令牌租户一致 → 零异常正常通过
|
|
115
124
|
os.environ["MDCG_TENANT"] = "tenantA"
|
|
116
125
|
buf2 = io.StringIO()
|
|
117
126
|
with contextlib.redirect_stderr(buf2):
|
|
118
127
|
mp2, merr2 = _build_principal()
|
|
119
|
-
check("MDCG_TENANT
|
|
128
|
+
check("MDCG_TENANT 同值:零异常零告警",
|
|
120
129
|
merr2 is None and MARK not in buf2.getvalue()
|
|
121
130
|
and getattr(mp2, "tenant", None) == "tenantA",
|
|
122
131
|
buf2.getvalue().strip()[:90])
|
|
@@ -127,6 +136,10 @@ def main():
|
|
|
127
136
|
else:
|
|
128
137
|
os.environ[k] = v
|
|
129
138
|
finally:
|
|
139
|
+
if old_reg is None:
|
|
140
|
+
os.environ.pop("MDCG_TENANT_REGISTRY", None)
|
|
141
|
+
else:
|
|
142
|
+
os.environ["MDCG_TENANT_REGISTRY"] = old_reg
|
|
130
143
|
shutil.rmtree(root, ignore_errors=True)
|
|
131
144
|
|
|
132
145
|
print(f"\n通过 {PASS} / 失败 {FAIL}")
|