@furongjun1999/dsh-memory 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +588 -552
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/discipline/harnesses.yaml +31 -0
- package/docs/discipline/templates/full.md.tmpl +1 -1
- package/docs/discipline/templates/rules.mdc.tmpl +1 -1
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
- package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
- package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +418 -0
- package/docs/eval/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +441 -0
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +398 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +498 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +669 -0
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +683 -0
- package/docs/eval//345/217/221/345/270/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -0
- package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +205 -0
- package/docs/eval//346/200/247/350/203/275/344/270/223/351/241/271_/345/206/267/346/237/245/350/257/242/344/270/216/345/206/205/345/255/230_v1.0.md +218 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +10 -0
- package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +646 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v18.md +183 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v19.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v20.md +283 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v21.md +224 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v22.md +223 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v23.md +293 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v24.md +303 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v25.md +230 -0
- package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +20 -0
- package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +12 -0
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +21 -9
- package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +7 -2
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +39 -3
- package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +220 -0
- package/docs/plans//347/234/237/346/272/220/347/264/242/345/274/225_/351/200/232/347/224/250/346/234/272/345/210/266_v0.3.md +227 -0
- package/docs/plans//350/234/202/345/267/242/346/250/241/345/236/213/345/257/206/351/222/245/351/205/215/347/275/256/351/235/242_v1.0.md +385 -0
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +460 -460
- package/dsh/cordis-patch-profile-web.example.yml +35 -0
- package/lib/bridge.js +24 -2
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +66 -0
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.d.ts +88 -0
- package/lib/init.js +302 -0
- package/lib/lib/prompt_safety.d.ts +52 -1
- package/lib/lib/prompt_safety.js +76 -1
- package/md_cg/audit.py +652 -379
- package/md_cg/bench6_arms.py +1 -1
- package/md_cg/bench_p0.py +1 -1
- package/md_cg/branches.py +2 -2
- package/md_cg/ccgc.py +1071 -1006
- package/md_cg/chain.py +1 -1
- package/md_cg/consistency.py +97 -9
- package/md_cg/consolidate.py +34 -9
- package/md_cg/crypto.py +59 -26
- package/md_cg/docindex.py +16 -1
- package/md_cg/evidence.py +585 -582
- package/md_cg/evolution.py +17 -1
- package/md_cg/export.py +1 -1
- package/md_cg/forgetting.py +309 -13
- package/md_cg/fsutil.py +454 -8
- package/md_cg/identity.py +3 -3
- package/md_cg/insight.py +24 -3
- package/md_cg/linkref.py +1 -1
- package/md_cg/logref.py +327 -0
- package/md_cg/mcp_server.py +4152 -3818
- package/md_cg/mdcg.py +4208 -3462
- package/md_cg/mdcos.py +4491 -4137
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +639 -575
- package/md_cg/protect.py +158 -11
- package/md_cg/protocol.py +41 -2
- package/md_cg/provenance.py +22 -4
- package/md_cg/reach.py +4 -4
- package/md_cg/readcache.py +76 -15
- package/md_cg/reconcile.py +1 -1
- package/md_cg/refindex.py +246 -37
- package/md_cg/refine.py +1 -1
- package/md_cg/review_cli.py +49 -4
- package/md_cg/routing.py +32 -5
- package/md_cg/run_tests.py +17 -0
- package/md_cg/scrub.py +30 -3
- package/md_cg/security.py +47 -1
- package/md_cg/self_state.py +5 -5
- package/md_cg/sources.py +23 -6
- package/md_cg/srcindex.py +352 -0
- package/md_cg/stg.py +47 -3
- package/md_cg/subgraph.py +2 -2
- package/md_cg/sustain.py +1299 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_b1_auto_id_multiproc.py +277 -0
- package/md_cg/test_b1b2_write_face.py +417 -0
- package/md_cg/test_b2_sensitivity_landing.py +223 -0
- package/md_cg/test_b3_merge_keeps_content.py +576 -0
- package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
- package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
- package/md_cg/test_c3_transient_read_negative.py +793 -0
- package/md_cg/test_c8_search_rrf_gates.py +502 -0
- package/md_cg/test_ccg_form_parity.py +188 -0
- package/md_cg/test_ccgc.py +28 -3
- package/md_cg/test_govern_directread.py +17 -4
- package/md_cg/test_h2_session_view_norm.py +233 -0
- package/md_cg/test_h4_sustain_snapshot.py +1377 -0
- package/md_cg/test_hive_ingest.py +285 -285
- package/md_cg/test_index_crossprocess_reload.py +301 -0
- package/md_cg/test_issue39_utf8_stdio.py +307 -16
- package/md_cg/test_issue43_default_policy.py +865 -0
- package/md_cg/test_legacy_p3_node_id_type.py +375 -0
- package/md_cg/test_linkref.py +10 -3
- package/md_cg/test_lock.py +2 -2
- package/md_cg/test_logref.py +1109 -0
- package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
- package/md_cg/test_m3_h9_semantic_guard.py +525 -0
- package/md_cg/test_mr_m2.py +15 -3
- package/md_cg/test_n130_verify_falsified_protect.py +1 -1
- package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
- package/md_cg/test_n176_link_trust.py +221 -0
- package/md_cg/test_n178_units_jobid_gate.py +212 -0
- package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
- package/md_cg/test_n195_writepath_reload.py +283 -0
- package/md_cg/test_n196_stale_gate_skip.py +169 -0
- package/md_cg/test_n197_n208_write_face_gates.py +471 -0
- package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
- package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
- package/md_cg/test_n201_proposal_visibility.py +382 -0
- package/md_cg/test_n202_session_notes_visibility.py +461 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
- package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
- package/md_cg/test_n209_verify_write_face_gates.py +461 -0
- package/md_cg/test_n212_n213_n224_generation_gates.py +513 -0
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
- package/md_cg/test_n225_nonobject_load.py +1110 -0
- package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
- package/md_cg/test_neg_condition_hits.py +333 -0
- package/md_cg/test_neg_tail_honesty.py +663 -0
- package/md_cg/test_none_id_write_guard.py +166 -0
- package/md_cg/test_opt_batch1_md_cg.py +451 -0
- package/md_cg/test_p1.py +5 -5
- package/md_cg/test_p11_consistency.py +161 -12
- package/md_cg/test_p26_refindex.py +2 -2
- package/md_cg/test_p27_docindex.py +777 -774
- package/md_cg/test_p28_refcheck.py +740 -13
- package/md_cg/test_p29_session_ingest_export.py +2 -2
- package/md_cg/test_p2_mcp.py +12 -1
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- package/md_cg/test_p9c_dedup_hints.py +276 -0
- package/md_cg/test_policy_required_ccg.py +426 -0
- package/md_cg/test_protocol.py +22 -0
- package/md_cg/test_rank_parity_score_mode.py +516 -0
- package/md_cg/test_read_face_input_gates.py +363 -0
- package/md_cg/test_read_face_semantics.py +489 -0
- package/md_cg/test_recall_face_guards.py +812 -0
- package/md_cg/test_rejected_credential_forms.py +188 -0
- package/md_cg/test_rejected_redact.py +146 -0
- package/md_cg/test_retr_s1b.py +2 -2
- package/md_cg/test_retr_s5.py +20 -7
- package/md_cg/test_review_cli_attribution.py +177 -0
- package/md_cg/test_review_cli_visibility.py +235 -0
- package/md_cg/test_review_conformance.py +21 -3
- package/md_cg/test_security_audit_v21.py +2 -2
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_v21r1_package_version.py +310 -0
- package/md_cg/test_writepipe.py +10 -4
- package/md_cg/tokens.py +225 -95
- package/md_cg/tool_face.py +4 -4
- package/md_cg/trust.py +49 -5
- package/md_cg/units.py +204 -6
- package/md_cg/weights.py +4 -4
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
- package/md_cg/writelimit.py +42 -8
- package/md_cg/writepipe.py +651 -554
- package/package.json +8 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/cli.ts +65 -0
- package/src/hooks.ts +229 -16
- package/src/init.ts +361 -0
- package/src/lib/prompt_safety.ts +88 -1
- package/zcode/AGENTS.md +10 -10
|
@@ -21,20 +21,63 @@ DSH 桥路径——Claude Code / code CLI 直接 mcp.json 接入不经桥,仍
|
|
|
21
21
|
(cwd=仓根,**不带** ``-X utf8``),env 显式设 ``PYTHONIOENCODING=gbk:strict``
|
|
22
22
|
模拟 CP936 控制台;按行分隔 JSON-RPC(initialize → notifications/initialized →
|
|
23
23
|
tools/call cg {op:"write", content:中文})。断言:响应行是合法 JSON、write 响应
|
|
24
|
-
不含 ``UnicodeEncodeError`` 且返回 ``ok``/``moved_to``
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
不含 ``UnicodeEncodeError`` 且返回 ``ok``/``moved_to`` 字段。修复前此面红
|
|
25
|
+
(进程在读侧崩、响应行缺失),修复后绿。
|
|
26
|
+
|
|
27
|
+
hive 面**两趟**(契约 c4/c5/c6;id 契约 v2 之后 `_submit` 改调 Rust 侧
|
|
28
|
+
``hive alloc-id`` 分配 id,故两趟口径不同、缺一不可):
|
|
29
|
+
· **失败趟(保留)**:``HIVE_EXE`` 指向**不存在的探针路径**,请求带一个不存在的
|
|
30
|
+
中文相对 context 路径 ⇒ 断言 ``ok=false`` 且原因**回显该中文路径**(逐字节相等)
|
|
31
|
+
——中文入参已过 JSON 解码与四槽闸(四槽闸在 context 闸之前)。
|
|
32
|
+
· **成功趟(c4 补回)**:``HIVE_EXE`` 指向**真实 release 二进制**
|
|
33
|
+
(``hive/target/release/hive.exe``,或环境变量 ``HIVE_EXE`` 覆盖),
|
|
34
|
+
``HIVE_JOBS_DIR`` 指向**本守卫自己的临时池**,喂四槽中文参数 spawn ⇒ 断言
|
|
35
|
+
``ok=true``、``job_id`` 非空且形如 ``h_…_…_…_NNNN``(四段中文形态)、任务目录
|
|
36
|
+
落在临时池内、中文逐字节往返(spec.json 与响应 slots)。**二进制缺失**
|
|
37
|
+
(未构建的新检出)⇒ 本趟**显式 SKIP 并打印期望路径**,不计入通过数;失败趟照旧
|
|
38
|
+
必须通过。
|
|
39
|
+
· 安全前提(真 exe 为什么安全,码上必须写明):``alloc-id`` 是**纯分配**通道
|
|
40
|
+
(Rust ``job::alloc_job_id`` 只 ``fs::create_dir`` 落池,不读不启动 serve);
|
|
41
|
+
且 ``HIVE_CONFIG`` 钉在不存在路径 ⇒ ``_ensure_serve`` 的拉起分支在
|
|
42
|
+
``serve_start.start()`` 的 ``load_config`` 即返回失败(serve_start.py:462-464),
|
|
43
|
+
Popen 那一步不执行——成功趟据此断言 ``serve.started is False`` 自证(失败趟的
|
|
44
|
+
响应是 context 闸的拒收,本就**没有** serve 字段,故该断言只落在成功趟)。
|
|
27
45
|
|
|
28
46
|
隔离纪律:MDCG_ROOT/MDCG_STATE_ROOT/MDCG_DATA_ROOT/MDCG_AUX_ROOT/
|
|
29
47
|
MDCG_TENANT_REGISTRY/HIVE_JOBS_DIR/HIVE_CONFIG 全部指向 tempfile.mkdtemp 临时
|
|
30
48
|
目录(登记表指向不存在的临时路径=空表回落),身份走既有豁免面
|
|
31
49
|
``MDCG_LEGACY_ENV_AUTH=1``(env 直连 designer,零令牌、不回落真实 ~/.mdcg),
|
|
32
|
-
MDCG_SUSTAIN=0
|
|
50
|
+
MDCG_SUSTAIN=0 关常驻循环;成功趟的池是**指针**(HIVE_JOBS_DIR)指向的临时目录,
|
|
51
|
+
并前后比对在役 ``hive/jobs`` 清单(只读)自证零触碰;finally rmtree——绝不触真实
|
|
52
|
+
令牌库与真实数据目录。
|
|
33
53
|
|
|
34
54
|
自检 floor:实弹断言低于 LIVE_FLOOR 视为失败——防止「spawn 面失效 → 假绿」。
|
|
55
|
+
SKIP 单列计数(PASS/FAIL/SKIP 三态):跳过项计入输出与汇总,**绝不计入通过数**。
|
|
56
|
+
|
|
57
|
+
④ 的两趟与定点变异自证(d1;**在临时物化副本上做,绝不改工作树**)
|
|
58
|
+
------------------------------------------------------------------------
|
|
59
|
+
本守卫不持变异自证装置(它的判据面是「真起进程喂 JSON-RPC」,逐条变异要另建一套
|
|
60
|
+
物化 + 重跑装置);改法是把「两趟是否还在」变成**一条判据**(3'·两趟齐备,见
|
|
61
|
+
`_two_passes_reason`:AST 读**仓面那一份**源码的 `main()` 语段查判据骨架),再用
|
|
62
|
+
一次性脚本在临时物化副本上实测三种态(**实测命令与读数,2026-09-30 本机**):
|
|
63
|
+
|
|
64
|
+
python -X utf8 <TEMP>/guardb_mut.py # 物化 1163 文件的副本,副本不含 hive/target
|
|
65
|
+
|
|
66
|
+
A 原样副本(副本无二进制 ⇒ 成功趟 SKIP,失败趟照旧跑)
|
|
67
|
+
rc=0 · 13 passed / 0 failed / **1 skipped**(live=7) · 打印
|
|
68
|
+
`[SKIP] 2e–2k 成功路径…` 与 `⚠ 显式 SKIP 1 项(未计入通过数)`
|
|
69
|
+
B **删掉成功趟**(2' 段整段退场,5375 字节)
|
|
70
|
+
rc=1 · 11 passed / **2 failed** · 红项恰为
|
|
71
|
+
{`3'·两趟齐备`(缺 binexe/ok is True/2g/started is False/2j 五处骨架), `4b`}
|
|
72
|
+
C 假阳性对照(失败趟内局部改名 `serr`→`serr_text`,判据一字不动)
|
|
73
|
+
rc=0 · 13 passed / 0 failed / 1 skipped —— 不误报
|
|
74
|
+
|
|
75
|
+
⚠ 与仓库其它守卫的差别(如实声明):A/C 两态里成功趟是 SKIP 而非真跑(副本无构建
|
|
76
|
+
产物)——**真跑**那条由仓面直跑覆盖(本机 rc=0、20 passed、live=13,成功趟真跑过)。
|
|
35
77
|
"""
|
|
36
78
|
from __future__ import annotations
|
|
37
79
|
|
|
80
|
+
import ast
|
|
38
81
|
import json
|
|
39
82
|
import os
|
|
40
83
|
import shutil
|
|
@@ -42,6 +85,21 @@ import subprocess
|
|
|
42
85
|
import sys
|
|
43
86
|
import tempfile
|
|
44
87
|
|
|
88
|
+
# ---------------------------------------------------------------- 入口自保证 UTF-8
|
|
89
|
+
# 约束(工作纪律第 15 条,d2):本调用必须在**任何文件/库 I/O 之前**——本守卫也是
|
|
90
|
+
# 一个进程入口(`python -X utf8 -m md_cg.test_issue39_utf8_stdio` / 直跑文件),它的
|
|
91
|
+
# 判据描述与判据名都含中文:在清空 PYTHONUTF8/PYTHONIOENCODING 的 cp936 控制台上
|
|
92
|
+
# 直跑,未接助手时第一条 `print` 即 UnicodeEncodeError 崩、rc=1 且**后半判据根本没
|
|
93
|
+
# 跑**——守卫守的就是这件事,自己不自保证说不过去。形态照 scripts/run_tests.py 的
|
|
94
|
+
# 最小写法(助手在仓根,不是 md_cg 包目录);被 import(本模块非 __main__)时助手只
|
|
95
|
+
# 置子进程继承面、绝不重启/退出(F6)。
|
|
96
|
+
_UTF8_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
97
|
+
if _UTF8_ROOT not in sys.path:
|
|
98
|
+
sys.path.insert(0, _UTF8_ROOT)
|
|
99
|
+
from utf8_boot import ensure_utf8 # noqa: E402
|
|
100
|
+
|
|
101
|
+
ensure_utf8(__file__)
|
|
102
|
+
|
|
45
103
|
try:
|
|
46
104
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
47
105
|
except AttributeError:
|
|
@@ -50,8 +108,41 @@ except AttributeError:
|
|
|
50
108
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
51
109
|
LIVE_FLOOR = 2 # 实弹断言下限(md_cg 面 ≥1 + hive 面 ≥1)
|
|
52
110
|
PROBE = "中文标题:编码守卫" # UTF-8 字节按 GBK strict 解码必非法(红态机制,见头注)
|
|
111
|
+
#: 探针用的中文**相对** context 路径(必不存在):`_t_spawn` 的 context 存在性闸会把
|
|
112
|
+
#: 它逐字节回显进 error ⇒ 一次调用同时证明「中文入参已被受理」与「中文出参编码正确」。
|
|
113
|
+
PROBE_CTX = "探针_中文上下文_无此文件.md"
|
|
114
|
+
#: 成功趟(c4)的四槽(前三槽;第四槽「编号」由 Rust 侧分配器给出)与
|
|
115
|
+
#: **存在**的中文 context 文件(逐字节往返的第二载体:它会被写进 spec.json)
|
|
116
|
+
PROBE_SLOTS = {"identity": "探针端", "task": "编码守卫", "unit": "验证单元"}
|
|
117
|
+
PROBE_ID_PREFIX = "h_探针端_编码守卫_验证单元_"
|
|
118
|
+
PROBE_CTX_OK_NAME = "探针_中文上下文_存在.md"
|
|
119
|
+
#: hive 二进制相对路径(成功趟用真二进制;见 `_hive_bin` 的两级查找)
|
|
120
|
+
HIVE_BIN_REL = ("hive", "target", "release",
|
|
121
|
+
"hive.exe" if os.name == "nt" else "hive")
|
|
122
|
+
|
|
123
|
+
#: 本守卫自身的仓内相对路径(`3'·两趟齐备` 这条自我锚读它——**读被判根的那一份**,
|
|
124
|
+
#: 不是内存里的活模块;故定点变异在临时物化副本上改它即可生效)
|
|
125
|
+
SELF_REL = "md_cg/test_issue39_utf8_stdio.py"
|
|
126
|
+
#: ④ 两趟齐备的**自我锚**:成功趟与失败趟在 `main()` 语段里必须出现的判据骨架。
|
|
127
|
+
#: 查法 = AST 取 `main` 的源码语段再查子串(见 `_two_passes_reason`)——**不在本常量处
|
|
128
|
+
#: 查**:这些字面量本身就在本常量里,若整文件查子串,删掉成功趟后锚点仍由常量自身满足
|
|
129
|
+
#: ⇒ 自我指涉假绿。取 `main()` 语段即把常量排除在外。
|
|
130
|
+
TWO_PASS_ANCHORS = (
|
|
131
|
+
# 失败趟(原口径,**必须保留**):ok=false 且原因回显中文 context 路径
|
|
132
|
+
"2b 中文 hive_spawn 逐字节往返",
|
|
133
|
+
'payload.get("ok") is False and PROBE_CTX in serr',
|
|
134
|
+
"2d 拒面未落任何任务目录",
|
|
135
|
+
# 成功趟(c4 补回):真 exe + 临时池 + ok=true + 四槽中文形态 + 未拉 serve + 零触碰
|
|
136
|
+
"binexe = _hive_bin()",
|
|
137
|
+
'payload.get("ok") is True',
|
|
138
|
+
"2g 任务目录落在守卫自己的临时池",
|
|
139
|
+
'(payload.get("serve") or {}).get("started") is False',
|
|
140
|
+
"2j 在役池零触碰",
|
|
141
|
+
)
|
|
142
|
+
#: 自我锚同时钉住的辅助函数面(删掉 `skip`/`_hive_bin` 即等于把 c4/c6 的手段拆掉)
|
|
143
|
+
TWO_PASS_FUNCS = ("skip", "_hive_bin", "_hive_env", "_pool_listing", "_under")
|
|
53
144
|
|
|
54
|
-
PASS, FAIL, LIVE = 0, 0, 0
|
|
145
|
+
PASS, FAIL, LIVE, SKIP = 0, 0, 0, 0
|
|
55
146
|
|
|
56
147
|
|
|
57
148
|
def check(name, cond, detail="", live=False):
|
|
@@ -66,6 +157,13 @@ def check(name, cond, detail="", live=False):
|
|
|
66
157
|
print(f" [FAIL] {name} {detail}")
|
|
67
158
|
|
|
68
159
|
|
|
160
|
+
def skip(name, reason):
|
|
161
|
+
"""显式跳过(c6):计入输出与汇总,**不计入通过数**(三态:PASS/FAIL/SKIP)。"""
|
|
162
|
+
global SKIP
|
|
163
|
+
SKIP += 1
|
|
164
|
+
print(f" [SKIP] {name}\n {reason}")
|
|
165
|
+
|
|
166
|
+
|
|
69
167
|
# 模拟 CP936 控制台前,先把父进程可能携带的「身份/路径/编码」注入面清干净——
|
|
70
168
|
# 守卫子进程只吃临时目录,绝不吃本机真实 ~/.mdcg / 真实 MDCG_ROOT。
|
|
71
169
|
_DIRTY_KEYS = (
|
|
@@ -107,15 +205,84 @@ def _mdcg_env(tmp: str) -> dict:
|
|
|
107
205
|
return env
|
|
108
206
|
|
|
109
207
|
|
|
110
|
-
def _hive_env(tmp: str) -> dict:
|
|
208
|
+
def _hive_env(tmp: str, exe: str | None = None) -> dict:
|
|
209
|
+
"""hive 面子进程 env。
|
|
210
|
+
|
|
211
|
+
生效条件:exe 为 None(失败趟)⇒ HIVE_EXE 钉在**不存在的探针路径**(`_alloc_job_id`
|
|
212
|
+
在 isfile 即抛显式 SubmitError);exe 给定时(成功趟)⇒ HIVE_EXE 指向该**真实**
|
|
213
|
+
二进制。两趟的 HIVE_CONFIG 一律指向不存在的路径:`_ensure_serve` 的拉起分支因此
|
|
214
|
+
在 `serve_start.start()` 的 `load_config` 即返回失败(serve_start.py:462-464),
|
|
215
|
+
Popen 那一步**不执行** ⇒ 真 exe 也绝不拉起在役 serve。HIVE_JOBS_DIR 一律指向本
|
|
216
|
+
守卫自己的临时池。
|
|
217
|
+
"""
|
|
111
218
|
env = _base_env(tmp)
|
|
112
219
|
env["HIVE_JOBS_DIR"] = os.path.join(tmp, "hivejobs")
|
|
113
|
-
#
|
|
220
|
+
# 指向不存在的配置与(缺省时)可执行文件:_ensure_serve 在 isfile 即返回,绝不拉起真 serve
|
|
114
221
|
env["HIVE_CONFIG"] = os.path.join(tmp, "hive_config_absent.json")
|
|
115
|
-
env["HIVE_EXE"] = os.path.join(tmp, "no_such_hive.exe")
|
|
222
|
+
env["HIVE_EXE"] = exe or os.path.join(tmp, "no_such_hive.exe")
|
|
116
223
|
return env
|
|
117
224
|
|
|
118
225
|
|
|
226
|
+
def _hive_bin() -> str:
|
|
227
|
+
"""成功趟用的**真实** hive 二进制(两级查找;不猜、不下载、不构建)。
|
|
228
|
+
|
|
229
|
+
① 环境变量 ``HIVE_EXE``(需 isfile)——使用者显式覆盖;
|
|
230
|
+
② 本仓 ``hive/target/release/hive[.exe]``——即它**期望存在**的那条路径:
|
|
231
|
+
两级都不在时也返回它,供 SKIP 理由把期望路径原样打印出来(c6)。
|
|
232
|
+
"""
|
|
233
|
+
env = os.environ.get("HIVE_EXE")
|
|
234
|
+
if env and os.path.isfile(env):
|
|
235
|
+
return env
|
|
236
|
+
return os.path.join(ROOT, *HIVE_BIN_REL)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _pool_listing() -> list | None:
|
|
240
|
+
"""在役 `hive/jobs` 的目录项排序清单(**只读**;不存在 ⇒ None)。
|
|
241
|
+
|
|
242
|
+
c5 的自证面:成功趟前后各取一次,清单必须逐字节一致——守卫的池是 HIVE_JOBS_DIR
|
|
243
|
+
指针指向的临时目录,在役池不该因本守卫增减任何一项。
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
return sorted(os.listdir(os.path.join(ROOT, "hive", "jobs")))
|
|
247
|
+
except OSError:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _under(path: str, parent: str) -> bool:
|
|
252
|
+
"""path 是否落在 parent(含 parent 自身)之下(绝对路径比对)——临时池自证用。"""
|
|
253
|
+
p, q = os.path.abspath(path), os.path.abspath(parent)
|
|
254
|
+
return p == q or p.startswith(q.rstrip("\\/") + os.sep)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _two_passes_reason() -> str | None:
|
|
258
|
+
"""④ 两趟齐备的自我锚:成功趟与失败趟的判据骨架都在本文件 `main()` 语段里。
|
|
259
|
+
|
|
260
|
+
生效条件:读 `<ROOT>/md_cg/test_issue39_utf8_stdio.py`(git 追踪面里的**那一份**,
|
|
261
|
+
与定点变异在临时物化副本上改的是同一路径——故「成功趟被删掉」在本判据上转红,
|
|
262
|
+
见头注的定点变异实测记录)→ AST 取 `main` 函数语段 + 全模块函数名集合 → 逐项查
|
|
263
|
+
`TWO_PASS_ANCHORS`/`TWO_PASS_FUNCS`;全在返回 None,缺任一项返回人类可读的缺失清单
|
|
264
|
+
(**判据面漂移不得静默**)。源码不可读/不可解析时同样返回原因(fail-closed)。
|
|
265
|
+
不适用条件:不判「判据是否真的在跑」(那是 4a/4b 的 floor 与 2e–2k 的实弹面),
|
|
266
|
+
本判据只回答「两趟的代码是否还在」——即「不许把某趟悄悄删掉」。
|
|
267
|
+
"""
|
|
268
|
+
ap = os.path.join(ROOT, SELF_REL.replace("/", os.sep))
|
|
269
|
+
try:
|
|
270
|
+
with open(ap, encoding="utf-8", errors="replace") as fh:
|
|
271
|
+
src = fh.read()
|
|
272
|
+
tree = ast.parse(src, filename=SELF_REL)
|
|
273
|
+
except (OSError, SyntaxError) as e:
|
|
274
|
+
return f"本守卫源码不可读/不可解析({ap}):{type(e).__name__}: {e}"
|
|
275
|
+
mains = [n for n in ast.walk(tree)
|
|
276
|
+
if isinstance(n, ast.FunctionDef) and n.name == "main"]
|
|
277
|
+
if not mains:
|
|
278
|
+
return "本守卫源码里找不到 main()(判据面已漂移)"
|
|
279
|
+
body = ast.get_source_segment(src, mains[0]) or ""
|
|
280
|
+
names = {n.name for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)}
|
|
281
|
+
miss = [t for t in TWO_PASS_ANCHORS if t not in body]
|
|
282
|
+
miss += [f"函数 {fn}() 不存在" for fn in TWO_PASS_FUNCS if fn not in names]
|
|
283
|
+
return None if not miss else "缺:" + ";".join(miss)
|
|
284
|
+
|
|
285
|
+
|
|
119
286
|
def _rpc(rid, method, params=None):
|
|
120
287
|
msg = {"jsonrpc": "2.0", "id": rid, "method": method}
|
|
121
288
|
if params is not None:
|
|
@@ -171,6 +338,7 @@ def _main_body_has_call(src: str) -> bool:
|
|
|
171
338
|
|
|
172
339
|
def main():
|
|
173
340
|
tmp = tempfile.mkdtemp(prefix="mdcg_issue39_")
|
|
341
|
+
pool_before = _pool_listing() # 在役池快照(只读;c5 的前后比对基线)
|
|
174
342
|
try:
|
|
175
343
|
# ---- 1. md_cg 面实弹:GBK 代码页下写中文 ----
|
|
176
344
|
print("[1] md_cg stdio 实弹(PYTHONIOENCODING=gbk:strict,无 -X utf8)")
|
|
@@ -207,6 +375,12 @@ def main():
|
|
|
207
375
|
rc == 0, f"rc={rc} err_tail={err[-200:]!r}", live=True)
|
|
208
376
|
|
|
209
377
|
# ---- 2. hive 面实弹:同族 server 同款代码页 ----
|
|
378
|
+
# id 契约 v2(B8)下的口径调整(**已声明**,非静默放宽):`_submit` 不再自造 id,
|
|
379
|
+
# 改调 Rust 侧 `hive alloc-id`;本守卫的 `_hive_env` 把 `HIVE_EXE` 钉在**不存在的
|
|
380
|
+
# 路径**(绝不拉起真 serve),故「ok=true+job_id」这条成功路径在本面**结构性不可
|
|
381
|
+
# 达**。改判的仍是同一件事——中文能否原样穿过 stdio 双程:请求带中文四槽 + 一个
|
|
382
|
+
# 不存在的中文相对 context 路径,断言 ok=False 且原因**回显该中文路径**(逐字节
|
|
383
|
+
# 相等),且错误不是「缺四槽」⇒ 中文入参已过 JSON 解码与四槽闸。
|
|
210
384
|
print("[2] hive stdio 实弹(同款 PYTHONIOENCODING=gbk:strict)")
|
|
211
385
|
rc2, out2, err2 = _talk(
|
|
212
386
|
[sys.executable, "-m", "hive.hive_mcp.mcp_server"],
|
|
@@ -214,7 +388,9 @@ def main():
|
|
|
214
388
|
_rpc(1, "initialize", {}),
|
|
215
389
|
_rpc(None, "notifications/initialized"),
|
|
216
390
|
_rpc(3, "tools/call", {"name": "hive_spawn", "arguments": {
|
|
217
|
-
"model": "probe-model", "user_prompt": PROBE
|
|
391
|
+
"model": "probe-model", "user_prompt": PROBE,
|
|
392
|
+
"identity": "探针端", "task": "编码守卫", "unit": "验证单元",
|
|
393
|
+
"context_files": [PROBE_CTX]}}),
|
|
218
394
|
])
|
|
219
395
|
resps2 = _resp_lines(out2)
|
|
220
396
|
init2 = _find_resp(resps2, 1)
|
|
@@ -230,23 +406,124 @@ def main():
|
|
|
230
406
|
payload = json.loads(
|
|
231
407
|
(sp.get("result") or {}).get("content", [{}])[0].get(
|
|
232
408
|
"text", "{}"))
|
|
233
|
-
|
|
409
|
+
serr = payload.get("error") or ""
|
|
410
|
+
sp_ok = (payload.get("ok") is False and PROBE_CTX in serr
|
|
411
|
+
and "四槽" not in serr)
|
|
234
412
|
except (ValueError, AttributeError, IndexError):
|
|
235
413
|
sp_ok = False
|
|
236
|
-
check("2b 中文 hive_spawn
|
|
414
|
+
check("2b 中文 hive_spawn 逐字节往返(ok=false 且原因回显中文 context 路径;"
|
|
415
|
+
"探针 exe 不存在,绝不拉真 serve)",
|
|
237
416
|
sp_ok, f"resp={str(sp)[:200]}", live=True)
|
|
238
417
|
check("2c hive 面无 UnicodeEncodeError",
|
|
239
418
|
"UnicodeEncodeError" not in out2
|
|
240
419
|
and "UnicodeEncodeError" not in err2,
|
|
241
420
|
f"err_tail={err2[-160:]!r}", live=True)
|
|
242
|
-
#
|
|
421
|
+
# 探针诚实边界(id 契约 v2 下重述):本探针的拒面发生在**分配之前**(context
|
|
422
|
+
# 存在性闸),故守卫自己的临时池应零任务残留——在役池自然零触碰。
|
|
243
423
|
jobs_dir = os.path.join(tmp, "hivejobs")
|
|
244
424
|
probe_jobs = [n for n in (os.listdir(jobs_dir) if os.path.isdir(jobs_dir)
|
|
245
425
|
else []) if n.startswith("h")]
|
|
246
|
-
check("2d
|
|
247
|
-
len(probe_jobs) ==
|
|
426
|
+
check("2d 拒面未落任何任务目录(临时池零残留 ⇒ 隔离面自证)",
|
|
427
|
+
len(probe_jobs) == 0,
|
|
248
428
|
f"probe_jobs={probe_jobs}")
|
|
249
429
|
|
|
430
|
+
# ---- 2'. hive **成功路径**(c4/c5/c6):真 exe + 守卫自己的临时池 ----
|
|
431
|
+
# 为什么真 exe 是安全的(关键前提,必须写在码上):
|
|
432
|
+
# ① 本趟走的是 `hive_spawn` → `_submit` → `_alloc_job_id` → `hive alloc-id`;
|
|
433
|
+
# alloc-id 是**纯分配**通道(Rust `job::alloc_job_id` 只 fs::create_dir
|
|
434
|
+
# 落池 + 回显 job_id),**不读也不启动任何 serve**;
|
|
435
|
+
# ② `HIVE_CONFIG` 仍钉在不存在路径 ⇒ `_ensure_serve` 的拉起分支即使被走到,
|
|
436
|
+
# 也在 `serve_start.start()` 的 `load_config` 即返回 {"ok": False,…}
|
|
437
|
+
# (serve_start.py:462-464),`Popen([EXE, "serve", …])` 不执行;
|
|
438
|
+
# ③ 池是 HIVE_JOBS_DIR 指针指向的**临时**目录 ⇒ 在役池零触碰(2g/2j 自证)。
|
|
439
|
+
print("[2'] hive 成功路径实弹(真 exe + 守卫自己的临时池;alloc-id 不启动 serve)")
|
|
440
|
+
binexe = _hive_bin()
|
|
441
|
+
if not os.path.isfile(binexe):
|
|
442
|
+
skip("2e–2k 成功路径(ok=true + job_id 四槽中文形态 + 目录落临时池)",
|
|
443
|
+
f"hive 二进制缺失:期望 {binexe}(未构建的新检出 ⇒ 先在 hive/ 下 "
|
|
444
|
+
f"cargo build --release,或用环境变量 HIVE_EXE 指向可用二进制)"
|
|
445
|
+
f"——成功趟**未执行**,不计入通过数;失败趟(2a–2d)已照旧执行")
|
|
446
|
+
else:
|
|
447
|
+
ctx = os.path.join(tmp, PROBE_CTX_OK_NAME)
|
|
448
|
+
with open(ctx, "w", encoding="utf-8", newline="\n") as fh:
|
|
449
|
+
fh.write("中文内容:上下文逐字节往返探针\n")
|
|
450
|
+
rc2b, out2b, err2b = _talk(
|
|
451
|
+
[sys.executable, "-m", "hive.hive_mcp.mcp_server"],
|
|
452
|
+
_hive_env(tmp, binexe), [
|
|
453
|
+
_rpc(1, "initialize", {}),
|
|
454
|
+
_rpc(None, "notifications/initialized"),
|
|
455
|
+
_rpc(3, "tools/call", {"name": "hive_spawn", "arguments": {
|
|
456
|
+
"model": "probe-model", "user_prompt": PROBE,
|
|
457
|
+
"identity": PROBE_SLOTS["identity"],
|
|
458
|
+
"task": PROBE_SLOTS["task"],
|
|
459
|
+
"unit": PROBE_SLOTS["unit"],
|
|
460
|
+
"context_files": [ctx]}}),
|
|
461
|
+
])
|
|
462
|
+
resps2b = _resp_lines(out2b)
|
|
463
|
+
init2b = _find_resp(resps2b, 1)
|
|
464
|
+
check("2e 真 exe 下 initialize 握手存活(组帧未被 alloc-id 打断)",
|
|
465
|
+
isinstance(init2b, dict)
|
|
466
|
+
and (init2b.get("result") or {}).get("serverInfo", {}).get(
|
|
467
|
+
"name") == "hive-mcp",
|
|
468
|
+
f"rc={rc2b} lines={len(resps2b)} err_tail={err2b[-200:]!r}",
|
|
469
|
+
live=True)
|
|
470
|
+
sp2 = _find_resp(resps2b, 3)
|
|
471
|
+
payload = {}
|
|
472
|
+
if isinstance(sp2, dict):
|
|
473
|
+
try:
|
|
474
|
+
payload = json.loads(
|
|
475
|
+
(sp2.get("result") or {}).get("content", [{}])[0].get(
|
|
476
|
+
"text", "{}"))
|
|
477
|
+
except (ValueError, AttributeError, IndexError):
|
|
478
|
+
payload = {}
|
|
479
|
+
jid = payload.get("job_id") or ""
|
|
480
|
+
jobs_dir = os.path.join(tmp, "hivejobs")
|
|
481
|
+
jdir = os.path.join(jobs_dir, jid) if jid else ""
|
|
482
|
+
tail4 = jid[len(PROBE_ID_PREFIX):] if jid.startswith(
|
|
483
|
+
PROBE_ID_PREFIX) else ""
|
|
484
|
+
check("2f 成功路径:ok=true 且 job_id 非空、形如 h_…_…_…_NNNN"
|
|
485
|
+
"(四段中文形态 = 四槽齐备且编号 4 位定宽)",
|
|
486
|
+
payload.get("ok") is True and bool(jid)
|
|
487
|
+
and jid.startswith(PROBE_ID_PREFIX)
|
|
488
|
+
and len(tail4) == 4 and tail4.isdigit() and jid.count("_") == 4,
|
|
489
|
+
f"rc={rc2b} payload={json.dumps(payload, ensure_ascii=False)[:220]}",
|
|
490
|
+
live=True)
|
|
491
|
+
check("2g 任务目录落在守卫自己的临时池(c5 自证:父目录 == 临时池 ∧ 路径在 "
|
|
492
|
+
"tmp 下 ∧ 不在在役 hive/jobs 下 ∧ 响应 jobs_dir 回显同一池)",
|
|
493
|
+
bool(jid) and os.path.isdir(jdir)
|
|
494
|
+
and os.path.dirname(os.path.abspath(jdir))
|
|
495
|
+
== os.path.abspath(jobs_dir)
|
|
496
|
+
and _under(jdir, tmp)
|
|
497
|
+
and not _under(jdir, os.path.join(ROOT, "hive", "jobs"))
|
|
498
|
+
and os.path.abspath(payload.get("jobs_dir") or "")
|
|
499
|
+
== os.path.abspath(jobs_dir),
|
|
500
|
+
f"jid={jid!r} jobs_dir={payload.get('jobs_dir')!r} 临时池={jobs_dir!r}",
|
|
501
|
+
live=True)
|
|
502
|
+
check("2h 真 exe 下**未拉起 serve**(alloc-id 不启动 serve;HIVE_CONFIG 缺席"
|
|
503
|
+
" ⇒ 拉起分支在 load_config 即返回)",
|
|
504
|
+
(payload.get("serve") or {}).get("started") is False,
|
|
505
|
+
f"serve={payload.get('serve')!r}", live=True)
|
|
506
|
+
spec_ok, spec_seen = False, None
|
|
507
|
+
try:
|
|
508
|
+
with open(os.path.join(jdir, "spec.json"), encoding="utf-8") as fh:
|
|
509
|
+
spec_seen = json.load(fh)
|
|
510
|
+
spec_ok = (spec_seen.get("user_prompt") == PROBE
|
|
511
|
+
and spec_seen.get("context_files") == [ctx])
|
|
512
|
+
except (OSError, ValueError):
|
|
513
|
+
spec_ok = False
|
|
514
|
+
check("2i 成功趟的中文逐字节往返(响应 slots 与 spec.json 的 "
|
|
515
|
+
"user_prompt/context_files 皆与请求逐字节相等)",
|
|
516
|
+
spec_ok and payload.get("slots") == dict(PROBE_SLOTS),
|
|
517
|
+
f"slots={payload.get('slots')!r} spec={json.dumps(spec_seen, ensure_ascii=False)[:200] if spec_seen else None!r}",
|
|
518
|
+
live=True)
|
|
519
|
+
check("2j 在役池零触碰(hive/jobs 清单前后逐字节一致)",
|
|
520
|
+
_pool_listing() == pool_before,
|
|
521
|
+
f"before={pool_before!r} after={_pool_listing()!r}")
|
|
522
|
+
check("2k 成功趟全程无 UnicodeEncodeError",
|
|
523
|
+
"UnicodeEncodeError" not in out2b
|
|
524
|
+
and "UnicodeEncodeError" not in err2b,
|
|
525
|
+
f"rc={rc2b} err_tail={err2b[-160:]!r}", live=True)
|
|
526
|
+
|
|
250
527
|
# ---- 3. 源断言:两 server 的 main() 首段强制 UTF-8 ----
|
|
251
528
|
print("[3] 源断言(main() 首段 _force_utf8_stdio())")
|
|
252
529
|
for rel in ("md_cg/mcp_server.py", "hive/hive_mcp/mcp_server.py"):
|
|
@@ -257,15 +534,29 @@ def main():
|
|
|
257
534
|
_main_body_has_call(src),
|
|
258
535
|
"缺定义或 main() 首段未调用(必须早于一切 stderr 中文写与 stdin 读取)")
|
|
259
536
|
|
|
537
|
+
# ---- 3'. 两趟齐备(④ 自我锚;定点变异自证:删掉成功趟 ⇒ 本条转红)----
|
|
538
|
+
_why = _two_passes_reason()
|
|
539
|
+
check("3'·两趟齐备(成功趟 + 失败趟的判据骨架都在 main() 语段里;"
|
|
540
|
+
"判据面读的是仓面那一份源码,删趟即转红)",
|
|
541
|
+
_why is None, _why or "")
|
|
542
|
+
|
|
260
543
|
# ---- 4. 守卫自检 floor:实弹断言不足即假绿 ----
|
|
261
544
|
print("[4] 守卫自检")
|
|
262
545
|
check(f"4a 实弹断言数 ≥ {LIVE_FLOOR}(防 spawn/扫描面失效假绿)",
|
|
263
546
|
LIVE >= LIVE_FLOOR, f"LIVE={LIVE}")
|
|
547
|
+
_bin_ok = os.path.isfile(_hive_bin())
|
|
548
|
+
check("4b 成功趟的取舍与二进制是否在盘一致(在盘 ⇒ 必须真跑过、零 SKIP;"
|
|
549
|
+
"不在盘 ⇒ 必须显式 SKIP——不许静默缺趟)",
|
|
550
|
+
(SKIP == 0) if _bin_ok else (SKIP >= 1),
|
|
551
|
+
f"bin={_hive_bin()!r} exists={_bin_ok} SKIP={SKIP} LIVE={LIVE}")
|
|
264
552
|
finally:
|
|
265
553
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
266
554
|
|
|
267
|
-
print(f"\n=== issue39 utf8 stdio tests: {PASS} passed, {FAIL} failed "
|
|
268
|
-
f"(live={LIVE}) ===")
|
|
555
|
+
print(f"\n=== issue39 utf8 stdio tests: {PASS} passed, {FAIL} failed, "
|
|
556
|
+
f"{SKIP} skipped (live={LIVE}) ===")
|
|
557
|
+
if SKIP:
|
|
558
|
+
print(f"⚠ 显式 SKIP {SKIP} 项(未计入通过数;理由见上方 [SKIP] 行)"
|
|
559
|
+
f"——本守卫**不因 SKIP 判绿**")
|
|
269
560
|
sys.exit(1 if FAIL else 0)
|
|
270
561
|
|
|
271
562
|
|