@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,426 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""写入闸门必需要素(CCG 六要素)守卫:policy.required 收窄到 text + 缺失清单可操作。
|
|
3
|
+
|
|
4
|
+
背景(规格:CCG 六要素强制与缺失报错):
|
|
5
|
+
· 规则库 `required` 原先对所有 content_kind 生效、缺失项只列前 3 个且显示**正则串**
|
|
6
|
+
(`缺少必需要素:['(?m)^#\\s*执行', ...]`)——写入方拿不到完整、可读的补齐清单;
|
|
7
|
+
· 写入面 REJECT 的 hint 只有一句「这是审核闸门的正常行为…重试同样结果」,
|
|
8
|
+
把**可修正的缺要素**误导成重试无用(内容补全后重写即落盘,与政策违规不同)。
|
|
9
|
+
|
|
10
|
+
改动面(本守卫断言的对象):
|
|
11
|
+
· policy 新增 required / required_kinds / required_labels(data/policy.json);
|
|
12
|
+
· audit._rule_check 收 kind 参数:forbidden 对所有 kind 生效,required 只对
|
|
13
|
+
required_kinds 收窄内的 kind 生效(键缺失/为空 = 全部 kind,向后兼容);
|
|
14
|
+
缺失项全列 + 中文展示名 + detail 结构化缺失清单;
|
|
15
|
+
· writepipe._gate_audit 的 REJECT hint 分型:缺要素 → 列出缺失项与补齐指引;
|
|
16
|
+
命中禁止规则 → 保留「重试同样结果」原文案。
|
|
17
|
+
|
|
18
|
+
断言(9 组):
|
|
19
|
+
G1 六要素齐全 → ACCEPT(且 evidence 说明必需规则确被检查,detail 为空)
|
|
20
|
+
G2 缺 3 项 → REJECT;evidence 含 3 个中文名、无截断;detail.missing 恰 3 项且
|
|
21
|
+
顺序与 required 一致,missing_patterns 与之一一对应
|
|
22
|
+
G3 六项全缺 → REJECT;六个中文名全部出现,detail.missing 即六项
|
|
23
|
+
G4 无 required_labels / 长度不匹配 / 非列表 → 回落正则串且**不抛错**
|
|
24
|
+
G5 required_kinds=["text"]:kind=work_wip 不受 required 约束(forbidden 仍生效);
|
|
25
|
+
kind=text 仍受限
|
|
26
|
+
G6 forbidden 优先于 required(同时命中时 evidence 为「命中禁止规则…」且无缺失清单)
|
|
27
|
+
G7 required 为空、forbidden 非空 → 行为与改动前一致(不含禁止模式即 ACCEPT)
|
|
28
|
+
G8 写入面返回:缺要素时 hint 列出缺失项 + 补齐指引且不含「重试同样结果」;
|
|
29
|
+
命中禁止规则时 hint 保持原语义(写入链为真实 WritePipeline + audit 闸)
|
|
30
|
+
G9 accept 旁路(红线):临时库 propose 一条缺六要素正文后 review_decide(accept)
|
|
31
|
+
仍能落盘——历史待审条目的 accept 不因本次改动收紧
|
|
32
|
+
|
|
33
|
+
红基线(不靠推理,对**当前工作区源码**做定点变异取红基线):
|
|
34
|
+
python -X utf8 md_cg/test_policy_required_ccg.py --head-baseline
|
|
35
|
+
该模式读工作区 `md_cg/audit.py` 与 `md_cg/writepipe.py` 的源码,按 `_BASELINE_MUTATIONS`
|
|
36
|
+
逐处改回**改动前行为**(required 不收窄 / 缺失清单用正则串且截断前 3 且无 detail /
|
|
37
|
+
hint 不分型),装进临时假包(其余依赖由转发层指向真仓模块),跑**同一套** check 并断言
|
|
38
|
+
「红项集合 == 预期红项」。锚点漂移即报 ANCHOR-MISS 并 fail-closed(不静默放过)。
|
|
39
|
+
G1/G5b/G6/G7/G9 项在两态皆绿(改动前这些行为本就成立,规格也要求它们不变);
|
|
40
|
+
G8e(整链 default_pipeline 与最小装配同结果)只在绿态跑——假包无法整链装配。
|
|
41
|
+
|
|
42
|
+
为什么不再用 `git show HEAD:` 做基线源(2026-09-28 修,缺陷实证):HEAD 只在改动**尚未
|
|
43
|
+
提交**时才是「改动前」。本守卫自己的改动一提交(批次70 `fa634fe5`),HEAD 里的
|
|
44
|
+
audit.py/writepipe.py 就变成实现本身——红项恒为空、预期红项永远对不上;而这条自证已被
|
|
45
|
+
并进 `scripts/linux_verify.sh` 的判别力清单(批次77),容器腿一跑就红。改为定点变异后,
|
|
46
|
+
基线源与 git 历史/浅克隆彻底解耦,判别力长期有效(同批次 `test_neg_condition_hits` 的
|
|
47
|
+
`_BRANCH_MUTATIONS` 与 `test_token_lowercase_form` 的 `_old_parse_token` 是同一做法)。
|
|
48
|
+
|
|
49
|
+
实验纪律:全程**临时目录**(tempfile 建临时图 root 与临时 policy),`MDCG_POLICY_FILE`
|
|
50
|
+
只指向临时 policy,绝不写真实库、绝不改真实 `data/policy.json`;哑值一律非真凭据。
|
|
51
|
+
运行:退出码 0 = 全绿(红基线模式 0 = 红项集合符合预期)。
|
|
52
|
+
"""
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import argparse
|
|
56
|
+
import ast
|
|
57
|
+
import importlib
|
|
58
|
+
import json
|
|
59
|
+
import os
|
|
60
|
+
import shutil
|
|
61
|
+
import sys
|
|
62
|
+
import tempfile
|
|
63
|
+
|
|
64
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
65
|
+
|
|
66
|
+
from md_cg.mdcos import MdCGSecure # noqa: E402
|
|
67
|
+
from md_cg.security import Principal # noqa: E402
|
|
68
|
+
|
|
69
|
+
_REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
70
|
+
|
|
71
|
+
# CCG 六要素(顺序即 policy.required 的顺序,也是 detail.missing 的顺序)
|
|
72
|
+
SIX = ("功能名", "生效条件", "子功能", "执行", "验证方式", "不适用条件")
|
|
73
|
+
REQ_PATTERNS = tuple(r"(?m)^#\s*%s" % m for m in SIX)
|
|
74
|
+
|
|
75
|
+
# 哑值(**非真凭据**):仅用于触发 forbidden,不构成任何密钥形态
|
|
76
|
+
FORBIDDEN = r"FORBIDDEN_TOKEN_[A-Z]{3}"
|
|
77
|
+
FORBIDDEN_HIT = "FORBIDDEN_TOKEN_XYZ"
|
|
78
|
+
|
|
79
|
+
# 改动前行为(required 对所有 kind 生效、缺项截断前 3 且无 detail、hint 单文案)下应为红的项
|
|
80
|
+
EXPECTED_RED = ("G2b", "G2c", "G2d", "G3b", "G3c", "G4a", "G4b", "G4c",
|
|
81
|
+
"G5a", "G8b", "G8c")
|
|
82
|
+
|
|
83
|
+
_BASELINE_FILES = {"audit": "md_cg/audit.py", "writepipe": "md_cg/writepipe.py"}
|
|
84
|
+
|
|
85
|
+
# 定点变异表(基线源=工作区源码 + 本表;**不取 git HEAD**,见 docstring 末段的缺陷实证):
|
|
86
|
+
# 每条 (标签, 现实现锚点, 改回后的旧行为)。锚点缺失 → ANCHOR-MISS → fail-closed 退出。
|
|
87
|
+
_BASELINE_MUTATIONS = {
|
|
88
|
+
"audit": (
|
|
89
|
+
("required 收窄",
|
|
90
|
+
"req_active = bool(required) and (not kinds or str(kind) in kinds)",
|
|
91
|
+
"req_active = bool(required)"),
|
|
92
|
+
("缺失清单形态",
|
|
93
|
+
' return (REJECT,\n'
|
|
94
|
+
' "缺少必需要素:%s(补齐后重写即可,本条未入库)"\n'
|
|
95
|
+
' % "、".join(missing),\n'
|
|
96
|
+
' {"missing": missing, "missing_patterns": missing_patterns})',
|
|
97
|
+
' return (REJECT,\n'
|
|
98
|
+
' "缺少必需要素:%s" % "、".join(missing_patterns[:3]),\n'
|
|
99
|
+
' None)'),
|
|
100
|
+
),
|
|
101
|
+
"writepipe": (
|
|
102
|
+
("hint 分型",
|
|
103
|
+
'if missing or ev.startswith("缺少必需要素"):',
|
|
104
|
+
"if False:"),
|
|
105
|
+
),
|
|
106
|
+
}
|
|
107
|
+
# 假包里转发到真仓 md_cg.<name> 的模块。**必须覆盖基线源出现的全部相对 import**
|
|
108
|
+
# (含函数内延迟导入)——缺登记即 SHIM-MISS fail-closed,不许靠「那条路径没跑到」侥幸绿着。
|
|
109
|
+
_SHIMS = ("twophase", "trust", "linkref", "mcp_server", "mdcg", "forgetting",
|
|
110
|
+
"units", "coldverify", "hotcache", "hyperedge", "nodefile")
|
|
111
|
+
_SHIM_TPL = ("import md_cg.%s as _m\n"
|
|
112
|
+
"globals().update({k: v for k, v in vars(_m).items()\n"
|
|
113
|
+
" if not k.startswith('__')})\n")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _relative_import_names(src):
|
|
117
|
+
"""AST 抽取相对 import 的顶层模块名:`from . import a, b` 与 `from .x import y` 两形态。"""
|
|
118
|
+
names = set()
|
|
119
|
+
for node in ast.walk(ast.parse(src)):
|
|
120
|
+
if isinstance(node, ast.ImportFrom) and (node.level or 0) > 0:
|
|
121
|
+
if node.module:
|
|
122
|
+
names.add(node.module.split(".")[0])
|
|
123
|
+
else:
|
|
124
|
+
for alias in node.names:
|
|
125
|
+
names.add(alias.name.split(".")[0])
|
|
126
|
+
return names
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _check_shim_coverage(blobs):
|
|
130
|
+
"""fail-closed:基线源里出现的每个相对 import,必须要么是本体(blobs),要么已登记 _SHIMS。
|
|
131
|
+
|
|
132
|
+
为什么必须有这条(2026-09-30 血案):`md_cg/writepipe.py` 新增了函数内
|
|
133
|
+
`from .mdcg import …` / `from . import forgetting` 两处委托,而 `_SHIMS` 没跟上 ⇒
|
|
134
|
+
假包 `mdcg_head` 缺模块 ⇒ 调用期抛 `ModuleNotFoundError` ⇒ 被 `_try` 收成
|
|
135
|
+
`err`、`got` 退化为 `{}` ⇒ `hint=""` ⇒ G8b 的 `"重试同样结果" not in ""` **恒真**,
|
|
136
|
+
于是「守卫坏了」表现为「多出两个红项 + 一个假绿」,而不是一声明确的报错。
|
|
137
|
+
本判据把该缺口提前到装配期,实现再长出新依赖时必须显式登记,否则整腿 fail-closed。
|
|
138
|
+
"""
|
|
139
|
+
missing = []
|
|
140
|
+
for name, data in blobs.items():
|
|
141
|
+
for mod in sorted(_relative_import_names(data.decode("utf-8"))):
|
|
142
|
+
if mod not in blobs and mod not in _SHIMS:
|
|
143
|
+
missing.append("%s -> .%s" % (name, mod))
|
|
144
|
+
if missing:
|
|
145
|
+
raise SystemExit(
|
|
146
|
+
"SHIM-MISS:基线源引用了未登记的相对模块,假包会缺模块(调用期抛 "
|
|
147
|
+
"ModuleNotFoundError,断言会退化成假绿)——请把下列名字加入 _SHIMS"
|
|
148
|
+
"(真仓模块)或 _BASELINE_FILES(被变异本体):" + ";".join(missing))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
_seq = [0]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _body(omit=()):
|
|
155
|
+
"""构造 CCG 正文:六要素各一行(omit 中的要素整行略去)+ 一行正文。"""
|
|
156
|
+
lines = ["# %s:单元 要素守卫探针(%s)" % (m, m) for m in SIX if m not in omit]
|
|
157
|
+
return "\n".join(lines) + "\n\n正文:必需要素守卫探针(临时库,非真实记忆)。"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _policy(**kw):
|
|
161
|
+
"""与新 data/policy.json 同形的临时策略(只放守卫自用的哑 forbidden)。"""
|
|
162
|
+
p = {"forbidden": [FORBIDDEN], "required": list(REQ_PATTERNS),
|
|
163
|
+
"required_kinds": ["text"], "required_labels": list(SIX)}
|
|
164
|
+
p.update(kw)
|
|
165
|
+
return p
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _write_policy(tmp, obj, name):
|
|
169
|
+
path = os.path.join(tmp, name)
|
|
170
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
171
|
+
json.dump(obj, f, ensure_ascii=False)
|
|
172
|
+
return path
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _mk_cg(tmp, tag):
|
|
176
|
+
_seq[0] += 1
|
|
177
|
+
root = os.path.join(tmp, "root_%02d_%s" % (_seq[0], tag))
|
|
178
|
+
p = Principal(tenant="default", actor="t_ccg_req", role="designer",
|
|
179
|
+
can_write=True, can_admin=True)
|
|
180
|
+
return MdCGSecure(root, principal=p)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _audit(audit_mod, text, rules, kind="text"):
|
|
184
|
+
return audit_mod.audit(kind, {"content": text}, {"rules": rules})
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _write_face(wp_mod, cg, a):
|
|
188
|
+
"""真实写入链上只装 audit 闸(两态同源对拍用)。
|
|
189
|
+
|
|
190
|
+
为什么不整链:HEAD 假包的链上其余闸(deps/linkref/consistency)依赖真仓模块的
|
|
191
|
+
相对导入,装进假包会连带把工作区实现拉进基线态;audit 是该链**首个可短路闸**,
|
|
192
|
+
其 REJECT 出口与本最小装配一等价(绿态另有 G8e 用整链 default_pipeline 复核)。
|
|
193
|
+
"""
|
|
194
|
+
pipe = wp_mod.WritePipeline()
|
|
195
|
+
pipe.register_before("audit", wp_mod._gate_audit)
|
|
196
|
+
return pipe.execute(cg, dict(a))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _try(fn):
|
|
200
|
+
try:
|
|
201
|
+
return fn(), None
|
|
202
|
+
except Exception as exc: # noqa: BLE001
|
|
203
|
+
return None, "%s: %s" % (type(exc).__name__, exc)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _fake_pkg(tmp, blobs):
|
|
207
|
+
"""在临时目录搭假包 mdcg_head:HEAD 源 + 指向真仓模块的转发层。"""
|
|
208
|
+
root = os.path.join(tmp, "head_pkg")
|
|
209
|
+
pkg = os.path.join(root, "mdcg_head")
|
|
210
|
+
os.makedirs(pkg, exist_ok=True)
|
|
211
|
+
with open(os.path.join(pkg, "__init__.py"), "w", encoding="utf-8") as f:
|
|
212
|
+
f.write("# 临时假包(守卫自用):只装 HEAD 源与真仓模块的转发层\n")
|
|
213
|
+
for name, data in blobs.items():
|
|
214
|
+
with open(os.path.join(pkg, name + ".py"), "wb") as f:
|
|
215
|
+
f.write(data)
|
|
216
|
+
for name in _SHIMS:
|
|
217
|
+
with open(os.path.join(pkg, name + ".py"), "w", encoding="utf-8") as f:
|
|
218
|
+
f.write(_SHIM_TPL % name)
|
|
219
|
+
sys.path.insert(0, root)
|
|
220
|
+
importlib.invalidate_caches()
|
|
221
|
+
return root
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _baseline_sources():
|
|
225
|
+
"""基线源=工作区源码 × 定点变异表(模块名 → 字节)。锚点漂移即 fail-closed。"""
|
|
226
|
+
out = {}
|
|
227
|
+
for name, rel in _BASELINE_FILES.items():
|
|
228
|
+
path = os.path.join(_REPO, rel)
|
|
229
|
+
with open(path, encoding="utf-8") as f:
|
|
230
|
+
src = f.read()
|
|
231
|
+
for label, anchor, old in _BASELINE_MUTATIONS.get(name, ()):
|
|
232
|
+
if anchor not in src:
|
|
233
|
+
raise SystemExit(
|
|
234
|
+
"ANCHOR-MISS [%s/%s]:变异锚点在 %s 中不存在——实现改了却没同步"
|
|
235
|
+
"本表,红基线失效(fail-closed)。锚点首行:%s"
|
|
236
|
+
% (name, label, rel, anchor.split("\n")[0][:80]))
|
|
237
|
+
if src.count(anchor) != 1:
|
|
238
|
+
raise SystemExit(
|
|
239
|
+
"ANCHOR-AMBIGUOUS [%s/%s]:锚点在 %s 中出现 %d 次(须唯一)"
|
|
240
|
+
% (name, label, rel, src.count(anchor)))
|
|
241
|
+
src = src.replace(anchor, old)
|
|
242
|
+
out[name] = src.encode("utf-8")
|
|
243
|
+
return out
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def run_checks(tmp, audit_mod, wp_mod, full_face=False):
|
|
247
|
+
"""对给定 audit/writepipe 模块跑全套 check → [(id, ok, detail)],全临时目录。"""
|
|
248
|
+
out = []
|
|
249
|
+
|
|
250
|
+
def add(cid, ok, detail=""):
|
|
251
|
+
out.append((cid, bool(ok), str(detail)[:200]))
|
|
252
|
+
|
|
253
|
+
p1 = _write_policy(tmp, _policy(), "policy_full.json")
|
|
254
|
+
three = ("执行", "验证方式", "不适用条件")
|
|
255
|
+
miss3 = _body(three)
|
|
256
|
+
miss_all = "正文:无任何要素标记(临时库探针)"
|
|
257
|
+
|
|
258
|
+
# ---------------- G1 六要素齐全 → ACCEPT ----------------
|
|
259
|
+
os.environ["MDCG_POLICY_FILE"] = p1
|
|
260
|
+
r = _audit(audit_mod, _body(), _policy())
|
|
261
|
+
add("G1a", r["state"] == "ACCEPT", r["state"])
|
|
262
|
+
add("G1b", "6 条必需规则" in r["evidence"], r["evidence"])
|
|
263
|
+
add("G1c", r["detail"] is None, repr(r["detail"]))
|
|
264
|
+
|
|
265
|
+
# ---------------- G2 缺 3 项 ----------------
|
|
266
|
+
r = _audit(audit_mod, miss3, _policy())
|
|
267
|
+
want = "缺少必需要素:执行、验证方式、不适用条件(补齐后重写即可,本条未入库)"
|
|
268
|
+
add("G2a", r["state"] == "REJECT", r["state"])
|
|
269
|
+
add("G2b", r["evidence"] == want, r["evidence"])
|
|
270
|
+
add("G2c", (r["detail"] or {}).get("missing") == list(three),
|
|
271
|
+
(r["detail"] or {}).get("missing"))
|
|
272
|
+
add("G2d", (r["detail"] or {}).get("missing_patterns") == list(REQ_PATTERNS[3:]),
|
|
273
|
+
(r["detail"] or {}).get("missing_patterns"))
|
|
274
|
+
|
|
275
|
+
# ---------------- G3 六项全缺 ----------------
|
|
276
|
+
r = _audit(audit_mod, miss_all, _policy())
|
|
277
|
+
add("G3a", r["state"] == "REJECT", r["state"])
|
|
278
|
+
add("G3b", all(m in r["evidence"] for m in SIX), r["evidence"])
|
|
279
|
+
add("G3c", (r["detail"] or {}).get("missing") == list(SIX),
|
|
280
|
+
(r["detail"] or {}).get("missing"))
|
|
281
|
+
|
|
282
|
+
# ---------------- G4 labels 缺失 / 长度不匹配 / 非列表 → 回落正则串 ----------------
|
|
283
|
+
no_lab = {"forbidden": [FORBIDDEN], "required": list(REQ_PATTERNS),
|
|
284
|
+
"required_kinds": ["text"]}
|
|
285
|
+
got, err = _try(lambda: _audit(audit_mod, miss_all, dict(no_lab)))
|
|
286
|
+
add("G4a", err is None and got is not None
|
|
287
|
+
and got["state"] == "REJECT"
|
|
288
|
+
and (got["detail"] or {}).get("missing") == list(REQ_PATTERNS),
|
|
289
|
+
err or (got["detail"] or {}).get("missing"))
|
|
290
|
+
got, err = _try(lambda: _audit(audit_mod, miss_all,
|
|
291
|
+
dict(no_lab, required_labels=list(SIX[:3]))))
|
|
292
|
+
add("G4b", err is None and got is not None
|
|
293
|
+
and (got["detail"] or {}).get("missing") == list(REQ_PATTERNS),
|
|
294
|
+
err or (got["detail"] or {}).get("missing"))
|
|
295
|
+
got, err = _try(lambda: _audit(audit_mod, miss_all,
|
|
296
|
+
dict(no_lab, required_labels="ABCDEF")))
|
|
297
|
+
add("G4c", err is None and got is not None
|
|
298
|
+
and (got["detail"] or {}).get("missing") == list(REQ_PATTERNS),
|
|
299
|
+
err or (got["detail"] or {}).get("missing"))
|
|
300
|
+
|
|
301
|
+
# ---------------- G5 required_kinds 收窄 ----------------
|
|
302
|
+
r_wip = audit_mod.audit("work_wip", {"content": miss_all}, {"rules": _policy()})
|
|
303
|
+
add("G5a", r_wip["state"] == "ACCEPT" and "不适用" in r_wip["evidence"],
|
|
304
|
+
"%s / %s" % (r_wip["state"], r_wip["evidence"]))
|
|
305
|
+
r_txt = _audit(audit_mod, miss_all, _policy())
|
|
306
|
+
add("G5b", r_txt["state"] == "REJECT", r_txt["state"])
|
|
307
|
+
r_wip_fb = audit_mod.audit("work_wip", {"content": "正文 " + FORBIDDEN_HIT},
|
|
308
|
+
{"rules": _policy()})
|
|
309
|
+
add("G5c", r_wip_fb["state"] == "REJECT"
|
|
310
|
+
and "命中禁止规则" in r_wip_fb["evidence"], r_wip_fb["evidence"])
|
|
311
|
+
|
|
312
|
+
# ---------------- G6 forbidden 优先 ----------------
|
|
313
|
+
r = _audit(audit_mod, "正文 " + FORBIDDEN_HIT + ",且无任何要素标记", _policy())
|
|
314
|
+
add("G6a", r["state"] == "REJECT"
|
|
315
|
+
and r["evidence"].startswith("命中禁止规则"), r["evidence"])
|
|
316
|
+
add("G6b", r["detail"] is None, repr(r["detail"]))
|
|
317
|
+
|
|
318
|
+
# ---------------- G7 required 为空(改动前行为) ----------------
|
|
319
|
+
p7 = {"forbidden": [FORBIDDEN], "required": []}
|
|
320
|
+
r = _audit(audit_mod, miss_all, dict(p7))
|
|
321
|
+
add("G7a", r["state"] == "ACCEPT", r["state"])
|
|
322
|
+
r = _audit(audit_mod, "正文 " + FORBIDDEN_HIT, dict(p7))
|
|
323
|
+
add("G7b", r["state"] == "REJECT"
|
|
324
|
+
and r["evidence"] == "命中禁止规则:" + FORBIDDEN, r["evidence"])
|
|
325
|
+
add("G7c", r["detail"] is None, repr(r["detail"]))
|
|
326
|
+
|
|
327
|
+
# ---------------- G8 写入面返回(真实写入链 + audit 闸) ----------------
|
|
328
|
+
cg = _mk_cg(tmp, "g8")
|
|
329
|
+
a_miss = {"content": miss3, "content_kind": "text", "layer": "knowledge"}
|
|
330
|
+
got, err = _try(lambda: _write_face(wp_mod, cg, a_miss))
|
|
331
|
+
got = got or {}
|
|
332
|
+
add("G8a", err is None and got.get("ok") is False
|
|
333
|
+
and got.get("committed") is False and got.get("moved_to") == "rejected",
|
|
334
|
+
err or json.dumps(got, ensure_ascii=False, default=str)[:180])
|
|
335
|
+
hint = str(got.get("hint") or "")
|
|
336
|
+
# 必须带 `err is None and hint`:调用抛异常时 got 退化为 {} ⇒ hint="" ⇒
|
|
337
|
+
# `"重试同样结果" not in ""` 恒真,本项会**假绿**(2026-09-30 实测发生过一次)。
|
|
338
|
+
add("G8b", err is None and bool(hint) and "重试同样结果" not in hint,
|
|
339
|
+
err or hint)
|
|
340
|
+
add("G8c", all(m in hint for m in three) and "补齐后重写" in hint, hint)
|
|
341
|
+
got2, err2 = _try(lambda: _write_face(
|
|
342
|
+
wp_mod, cg, {"content": "正文 " + FORBIDDEN_HIT, "content_kind": "text",
|
|
343
|
+
"layer": "knowledge"}))
|
|
344
|
+
got2 = got2 or {}
|
|
345
|
+
add("G8d", err2 is None and got2.get("moved_to") == "rejected"
|
|
346
|
+
and "重试同样结果" in str(got2.get("hint") or ""),
|
|
347
|
+
err2 or str(got2.get("hint") or ""))
|
|
348
|
+
if full_face:
|
|
349
|
+
got3, err3 = _try(lambda: wp_mod.default_pipeline().execute(
|
|
350
|
+
cg, dict(a_miss)))
|
|
351
|
+
got3 = got3 or {}
|
|
352
|
+
add("G8e", err3 is None and str(got3.get("hint") or "") == hint
|
|
353
|
+
and got3.get("moved_to") == "rejected",
|
|
354
|
+
err3 or str(got3.get("hint") or ""))
|
|
355
|
+
|
|
356
|
+
# ---------------- G9 accept 旁路(红线) ----------------
|
|
357
|
+
cg9 = _mk_cg(tmp, "g9")
|
|
358
|
+
pr = cg9.propose("mem_ccg_accept", miss3, info=True, layer="knowledge")
|
|
359
|
+
dec = cg9.review_decide(pr["pid"], "accept")
|
|
360
|
+
add("G9a", bool(dec.get("ok")) and dec.get("node_id") == "mem_ccg_accept",
|
|
361
|
+
json.dumps(dec, ensure_ascii=False, default=str)[:180])
|
|
362
|
+
node = cg9.get("mem_ccg_accept") or {}
|
|
363
|
+
add("G9b", "# 功能名" in str(node.get("content") or "")
|
|
364
|
+
and "执行" not in str(node.get("content") or ""),
|
|
365
|
+
json.dumps(node, ensure_ascii=False, default=str)[:180])
|
|
366
|
+
return out
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def main():
|
|
370
|
+
ap = argparse.ArgumentParser(description="写入闸门必需要素(CCG 六要素)守卫")
|
|
371
|
+
ap.add_argument("--head-baseline", action="store_true",
|
|
372
|
+
help="取红基线:对工作区 audit.py / writepipe.py 做定点变异(改回改动前"
|
|
373
|
+
"行为)后装配临时假包并断言红项集合 == 预期红项(参数名沿用历史名,"
|
|
374
|
+
"源不再是 git HEAD)")
|
|
375
|
+
args = ap.parse_args()
|
|
376
|
+
|
|
377
|
+
tmp = tempfile.mkdtemp(prefix="ccg_required_")
|
|
378
|
+
saved = os.environ.pop("MDCG_POLICY_FILE", None)
|
|
379
|
+
try:
|
|
380
|
+
if args.head_baseline:
|
|
381
|
+
blobs = _baseline_sources()
|
|
382
|
+
_check_shim_coverage(blobs)
|
|
383
|
+
_fake_pkg(tmp, blobs)
|
|
384
|
+
print("[红基线] 源 = 工作区 md_cg/audit.py + md_cg/writepipe.py 经定点变异"
|
|
385
|
+
"(%d 处:%s;写入临时假包,不覆盖工作区文件)"
|
|
386
|
+
% (sum(len(v) for v in _BASELINE_MUTATIONS.values()),
|
|
387
|
+
"、".join(lb for v in _BASELINE_MUTATIONS.values() for lb, _, _ in v)))
|
|
388
|
+
audit_mod = importlib.import_module("mdcg_head.audit")
|
|
389
|
+
wp_mod = importlib.import_module("mdcg_head.writepipe")
|
|
390
|
+
results = run_checks(tmp, audit_mod, wp_mod, full_face=False)
|
|
391
|
+
else:
|
|
392
|
+
print("[绿态] 源 = 工作区 md_cg/audit.py + md_cg/writepipe.py")
|
|
393
|
+
import md_cg.audit as audit_mod
|
|
394
|
+
import md_cg.writepipe as wp_mod
|
|
395
|
+
results = run_checks(tmp, audit_mod, wp_mod, full_face=True)
|
|
396
|
+
finally:
|
|
397
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
398
|
+
if saved is not None:
|
|
399
|
+
os.environ["MDCG_POLICY_FILE"] = saved
|
|
400
|
+
else:
|
|
401
|
+
os.environ.pop("MDCG_POLICY_FILE", None)
|
|
402
|
+
|
|
403
|
+
reds = [cid for cid, ok, _ in results if not ok]
|
|
404
|
+
for cid, ok, detail in results:
|
|
405
|
+
print(" %s %s%s" % ("OK " if ok else "FAIL", cid,
|
|
406
|
+
"" if ok else " " + detail))
|
|
407
|
+
print("\n结果:%d pass / %d fail(红项:%s)"
|
|
408
|
+
% (len(results) - len(reds), len(reds), ", ".join(reds) or "无"))
|
|
409
|
+
if args.head_baseline:
|
|
410
|
+
if tuple(reds) == EXPECTED_RED:
|
|
411
|
+
print("红基线符合预期:变异态下缺要素清单不可读/被截断、required 未收窄、"
|
|
412
|
+
"hint 无补齐指引,共 %d 项分叉(%s)"
|
|
413
|
+
% (len(EXPECTED_RED), "、".join(EXPECTED_RED)))
|
|
414
|
+
return 0
|
|
415
|
+
print("红基线与预期不符——预期红项:" + "、".join(EXPECTED_RED))
|
|
416
|
+
return 1
|
|
417
|
+
if reds:
|
|
418
|
+
print("FAILED:" + "、".join(reds))
|
|
419
|
+
return 1
|
|
420
|
+
print("ALL OK:text 类必需要素缺失即拒并给出可读补齐清单;work_wip 不受 required "
|
|
421
|
+
"约束;accept 旁路未受影响")
|
|
422
|
+
return 0
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
if __name__ == "__main__":
|
|
426
|
+
sys.exit(main())
|
package/md_cg/test_protocol.py
CHANGED
|
@@ -124,6 +124,28 @@ check("A16 语法错误返回空列表且不抛异常",
|
|
|
124
124
|
check("A17 dispatch_ops 与 audit 同源(同一次提取口径)",
|
|
125
125
|
P.dispatch_ops()["ops"] == _a["actual"], P.dispatch_ops()["ops"])
|
|
126
126
|
|
|
127
|
+
# ---- A18~A20 扩展能力面契约登记(2026-09-30):wrapper 恒定键 + protect 负路由 ----
|
|
128
|
+
# 这两条不属五动词形状表,但同为 protocol.py 的真源声明——故用**源码级同源守卫**
|
|
129
|
+
# 钉住实现(照 A14 的教训:声明与实现各写一份就会漂移,宁可少断言不可假绿灯)。
|
|
130
|
+
_here = os.path.dirname(os.path.abspath(__file__))
|
|
131
|
+
_msrc = open(os.path.join(_here, "mcp_server.py"), encoding="utf-8").read()
|
|
132
|
+
_psrc = open(os.path.join(_here, "protect.py"), encoding="utf-8").read()
|
|
133
|
+
check("A18 action_source 四态登记与 mcp_server 实现同源(M4 wrapper 恒定键)",
|
|
134
|
+
P.ACTION_SOURCE_KEY == "action_source"
|
|
135
|
+
and tuple(P.ACTION_SOURCE_VALUES) == ("explicit", "sig", "default", "none")
|
|
136
|
+
and all(('"%s"' % v) in _msrc for v in P.ACTION_SOURCE_VALUES)
|
|
137
|
+
and ('setdefault("%s", act_source)' % P.ACTION_SOURCE_KEY) in _msrc,
|
|
138
|
+
"登记=%r 取值=%r" % (P.ACTION_SOURCE_KEY, P.ACTION_SOURCE_VALUES))
|
|
139
|
+
check("A19 protect.mark 负路由形态登记与 protect.py 实现同源(H9)",
|
|
140
|
+
P.PROTECT_MARK_NEG_ROUTE_ERROR == "node_not_found"
|
|
141
|
+
and P.PROTECT_MARK_NEG_ROUTE.get("ok") is False
|
|
142
|
+
and '{"ok": False, "error": "node_not_found", "node_id": node_id}' in _psrc,
|
|
143
|
+
"登记=%r" % (P.PROTECT_MARK_NEG_ROUTE,))
|
|
144
|
+
check("A20 audit 透出两条扩展面登记(客户端不必读源码)",
|
|
145
|
+
_a.get("action_source", {}).get("key") == P.ACTION_SOURCE_KEY
|
|
146
|
+
and _a.get("protect_neg_route", {}).get("error") == "node_not_found",
|
|
147
|
+
str({k: _a.get(k) for k in ("action_source", "protect_neg_route")}))
|
|
148
|
+
|
|
127
149
|
# ==================== B. 动态:进程内真调形状 ====================
|
|
128
150
|
print("-- B 进程内真调形状 --")
|
|
129
151
|
_root = tempfile.mkdtemp(prefix="mdcg_proto_")
|