@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,646 @@
|
|
|
1
|
+
# 编码面前置批:入口自保证 UTF-8 与文本模式 open 守卫(工程报告 v1.0)
|
|
2
|
+
|
|
3
|
+
| 项 | 值 |
|
|
4
|
+
|---|---|
|
|
5
|
+
| 批次 | 编码面前置批 = A 入口自保证 UTF-8 + B 文本模式 `open` 守卫与存量订正 |
|
|
6
|
+
| 基线 HEAD | `174cd25f1e07eb873924b867dc1b14d7d758ab0b`(feat/opt(batch3)) |
|
|
7
|
+
| 工作树 | 未提交:14 个 `M` + 6 个 `??`(本批新件 4 个:`utf8_boot.py`、`scripts/test_utf8_boot_guard.py`、`scripts/verify_open_encoding.py`、`scripts/test_verify_open_encoding.py`;另 `.zcode/` 与本报告文件本身)。本批未 `git add` / `commit` / `push` |
|
|
8
|
+
| 纪律依据 | 工作纪律第 15 条(全仓文本与路径一律 UTF-8;裸 `open` 走 locale 即 cp936) |
|
|
9
|
+
| 行号口径 | **正文行号一律为本报告作者本次读码所见的工作树当前态**;凡与批内早期回报不一致处均点名漂移(如 kill 标志行由 `:653` 漂到 `:673`;批内另出现的中间读数 `:671` 的来源处置见 §2.2 B9)。§2 的 `观察` 列保留读数原文(含当时行号) |
|
|
10
|
+
| 读数来源标记 | `【采集】`= 工作流采集/批内实跑记录;`【复核】`= 两项独立复核的读数;`【本次】`= 本报告作者本次实跑/读码所得 |
|
|
11
|
+
| 凭据 | 本报告不含任何凭据明文(本会话环境存在一个已失效的 `MDCG_TOKEN`,值一律不抄录) |
|
|
12
|
+
| 修订 | **第 2 版(读者反馈处置轮)**。相对第 1 版的改动:新增 §1.3(两套编号对照)、§4.4 的 clean env 全量读数、§4.5 第 9–15 条(本轮新增实跑)、§7(读者待决问题 10 条的判定与建议);订正 §2.2 B1 的 HEAD 计数(437/407→427/397)、§4.3 的结论措辞、§6.1 的论证结构(删去不成立的算术旁证);补 §2.1 A13/A13′ 的令牌闸前置、§4.1 的物化面口径与 M1 红项集成因、§6.3/§6.4 的实测后果;附清单补齐 14 个 `M` 的逐件归属。逐条处置对照见 §7 |
|
|
13
|
+
|
|
14
|
+
> **本件与同路径上一版的关系**:本路径此前是一份**设计稿**。该稿把 F2(fail-fast 指引不可照抄)、
|
|
15
|
+
> F3(三流显式传递无判据)、F6(被 import 时重启的静默危害)记为**未修**,与现树不一致
|
|
16
|
+
> (三条均已修且已被守卫覆盖,见 §3.1)。本工程报告据现树与本次实跑**覆盖该稿**,读者以本件为准。
|
|
17
|
+
> **披露(第 2 版补)**:该设计稿当时是 `??` 未追踪件(无 git 历史),被本件首版覆盖后**已无留存副本**
|
|
18
|
+
> (`docs/eval/` 下无第二份「编码面…」文件,`git log -- <本路径>` 无输出);如需保留设计稿内容,
|
|
19
|
+
> 只能从会话记录/批内采集文本重建,处置建议见 §7-Q8。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 0. 摘要与结论
|
|
24
|
+
|
|
25
|
+
本批把「Python 侧文本 I/O 与进程入口的 UTF-8 正确性」从**环境约定**(`PYTHONUTF8=1` 挂在
|
|
26
|
+
使用者 profile 与「每个入口记得设」上)改成**进程自保证 + 机械守卫**:
|
|
27
|
+
|
|
28
|
+
- **A · 入口自保证 UTF-8**:新增仓根助手 `utf8_boot.py`(纯 stdlib、零 `md_cg` 依赖),
|
|
29
|
+
七个进程入口在任何文件/库 I/O **之前**调用 `ensure_utf8(__file__)`;解释器未开 UTF-8 模式时
|
|
30
|
+
由助手以 `[exe, -X, utf8, ...]` **重启自身**(保留 `-m` 语义、显式传递三条标准流),
|
|
31
|
+
或用显式退出通道 fail-fast。守卫 `scripts/test_utf8_boot_guard.py`。
|
|
32
|
+
- **B · 文本模式 `open` 守卫与存量订正**:新增 AST 守卫 `scripts/verify_open_encoding.py`
|
|
33
|
+
(**只认内建 `open`**)与回放断言 `scripts/test_verify_open_encoding.py`;三目录内
|
|
34
|
+
**9 处**真实文本模式裸 `open` 逐点订正为显式 `encoding="utf-8"`,基线清零。
|
|
35
|
+
|
|
36
|
+
**结论(全部为本次实跑读数,命令与输出见 §4.5)**
|
|
37
|
+
|
|
38
|
+
| 面 | 读数 | 来源 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| A 守卫 `scripts/test_utf8_boot_guard.py` | **24/24 通过**(正向 11/11 + 定点变异 13/13)、rc=0、耗时 21.8s | 【本次】 |
|
|
41
|
+
| A 守卫 `--no-mutations` | 11/11 通过、rc=0、1.3s | 【本次】 |
|
|
42
|
+
| B 守卫 `scripts/verify_open_encoding.py` | rc=0:扫描面 489 文件 / 文本 `open` 844 / 二进制 `open` 92 / Attribute `.open` 66 | 【本次】 |
|
|
43
|
+
| B 守卫 `--self-test` | rc=0:判据锚 22/22、Attribute 对照 5 文件、**契约点名假阳性点位 5/5**、定点变异 3 目标 × 4 变体 | 【本次】 |
|
|
44
|
+
| B 回放断言 `scripts/test_verify_open_encoding.py` | **ALL PASS**(26 条 `assert`)、rc=0 | 【本次】 |
|
|
45
|
+
| B 守卫**自身**在 clean env(无 `PYTHONUTF8`、无 `-X utf8`) | **rc=1 + 打印 `✔` 时 `UnicodeEncodeError` 崩溃 + stdout 落 GBK 字节**(第 2 版新增实测;`rc=1` 与「存在违例」同码 ⇒ 会误导) | 【本次】§6.3/§7-Q7 |
|
|
46
|
+
| python 全量(clean env 重跑) | **269/269 通过**、5 跳过、rc=0、100.7s,且 runner 自身输出**合法 UTF-8** | 【本次】§4.4 |
|
|
47
|
+
| 独立 AST 复核(本报告作者自写扫描器) | 工作树 489 文件 / 二进制 92 / 文本裸 `open` **0**;HEAD 物化树 486 文件 / 二进制 **89** / 文本裸 `open` **9** | 【本次】 |
|
|
48
|
+
| python 全量 `python scripts/run_tests.py` | **269/269 通过**,5 跳过(依赖缺失/平台不符),rc=0,101.7s | 【本次】 |
|
|
49
|
+
| 容器两栈 | 退出码 **127 / 127**(两栈均未取得有效读数) | 【采集】——本轮**未复跑**,且本机 Docker daemon 未运行(§4.4) |
|
|
50
|
+
| 独立复核判定 | A **ACCEPT** / B **ACCEPT** | 【复核】 |
|
|
51
|
+
|
|
52
|
+
**三句结论**
|
|
53
|
+
1. 两项守卫在本次实跑中全绿(A 24/24、B 26 assert ALL PASS),A 的 13 项定点变异**逐项**命中预期红项集与退出码
|
|
54
|
+
——**这些绿读数都在「已开 UTF-8 模式的现场」取得**;B 守卫**自身**在未设 env 的现场会崩并吐 GBK 字节
|
|
55
|
+
(上表倒数第 6 行、§6.3),那是**守卫自身**的缺陷,不影响「存量缺口已清 0」的结论。
|
|
56
|
+
2. 文本裸 `open` 真缺口数经逐点读码由契约的「11」收敛为「9」并已 100% 订正;另 2 处是**误分类**
|
|
57
|
+
(模块内自建 `write_text` 函数的调用,编码早已显式;机械照做会 `TypeError` 打断脚本)。
|
|
58
|
+
3. 旧口径 `224` **出处不闭环且不可复现**(无命令/口径留存,§6.1),`103` 可精确复现但**混入 5 处非内建 `open` 语义**
|
|
59
|
+
(`os.open` / `io.open` / `gzip.open` / `tarfile.open`×2)故不可作缺口判据;唯一能同时对上源码、
|
|
60
|
+
且能逐点对到行号的是「**89 处内建二进制 + 9 处文本真缺口**(+5 处 Attribute 假阳性 = 103)」。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 1. 两项各自的定义与真实站点
|
|
65
|
+
|
|
66
|
+
### 1.1 A · 入口自保证 UTF-8
|
|
67
|
+
|
|
68
|
+
**定义**:每个进程入口在其自身进程内、且在**任何文件/库 I/O 之前**调用
|
|
69
|
+
`utf8_boot.ensure_utf8(__file__)`;当解释器未开 UTF-8 模式时,由助手以 `-X utf8` 重启自身
|
|
70
|
+
(保留 `-m` 模块语义、显式传递 stdin/stdout/stderr),或按显式退出通道 fail-fast 退出。
|
|
71
|
+
正确性不再挂在「环境变量 + 每个入口记得设」上。
|
|
72
|
+
|
|
73
|
+
**助手站点**([utf8_boot.py](../../utf8_boot.py),238 行 / 15 356 B / 无 BOM【本次】):
|
|
74
|
+
|
|
75
|
+
| 落点 | 行 | 语义 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `ensure_utf8()` 定义(唯一调用点) | `utf8_boot.py:201` | 三分支见下 |
|
|
78
|
+
| 置子进程继承面 `_set_child_env()` | `:101-104`,调用点 `:218` | `PYTHONUTF8=1` + `PYTHONIOENCODING=utf-8` 写入 `os.environ`(**先置再判**,与契约④字面优先) |
|
|
79
|
+
| ② utf8 已开 ⇒ 立即返回 | `:219-220` | 不重启、不打日志 |
|
|
80
|
+
| F6 被 import ⇒ 只置 env 即返回 | `:221-222`+`_caller_is_process_entry()` `:136-149` | 用「调用帧 `f_globals` is `sys.modules['__main__'].__dict__`」的**同一性**判定;`-m` 与直跑文件同判 |
|
|
81
|
+
| ③ fail-fast | `:224-226`(`LINGSHU_UTF8_NO_REEXEC` 真值 ⇒ 指引+`SystemExit(2)`);常量 `NO_REEXEC_ENV :83`、`EXIT_UTF8_REQUIRED=2 :85` | 指引文案见 `_guidance() :164-181` |
|
|
82
|
+
| 重启 | `:227-237`:`reexec_argv() :153-160` → `subprocess.run(argv, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)` → `raise SystemExit(proc.returncode)` | `-m` 形态重建为 `[exe,"-X","utf8","-m",<模块名>,*sys.argv[1:]]`;`-c`/`-` 不重启,`SystemExit(3)`(`_EXIT_UNREBUILDABLE :91`) |
|
|
83
|
+
| 指引写 stderr | `_emit() :185-197` | 优先 `sys.stderr.buffer.write(text.encode("utf-8"))`,不随控制台代码页漂移 |
|
|
84
|
+
| 单点判别 `-m` 形态 | `_m_module_name() :108-119`,被 `launch_hint() :123-132` 与 `reexec_argv()` **共用** | F2 的修法本体:两处各写一份必然漂移 |
|
|
85
|
+
| 禁用 `os.exec*` | 模块 docstring `:55-58` | MCP 是字节组帧协议,重启必须显式传三流 |
|
|
86
|
+
|
|
87
|
+
> **本表的分支标号(②/③/F6)按代码执行顺序排**:`:218` 先置 env → `:219` 判 utf8 模式 → `:221` 判调用方是否进程入口 → `:224` fail-fast → `:227` 重启。
|
|
88
|
+
> §3.1 另用一套「路 0 / 1 / 2 / 3 / 3′」编号(取自助手模块 docstring `:25-36` 的既有编号,**不是**代码顺序)。
|
|
89
|
+
> 两套编号的**逐项对照表见 §1.3**,读者不必自行猜对应关系。
|
|
90
|
+
|
|
91
|
+
**七个入口的真实站点**(本报告作者逐文件读出【本次】;`_UTF8_ROOT` = 仓库根,形态照
|
|
92
|
+
`hive/exec.py::_md_cg_import` 的最小写法):
|
|
93
|
+
|
|
94
|
+
| 入口 | 注释锚 | `_UTF8_ROOT` | `sys.path.insert` | `from utf8_boot import …` | `ensure_utf8(__file__)` | `def main` / `__main__` guard |
|
|
95
|
+
|---|---|---|---|---|---|---|
|
|
96
|
+
| `hive/hive_mcp/mcp_server.py`(ZCode/DSH 直连 stdio MCP) | `:50-58` | `:59-60` | `:61-62` | `:63` | `:65` | `:942` / `:964` |
|
|
97
|
+
| `md_cg/mcp_server.py`(认知图 stdio MCP) | `:46` | `:54` | `:55-56` | `:57` | `:59` | `:3837` / `:4038` |
|
|
98
|
+
| `hive/exec.py`(执行器子进程) | `:98` | `:106` | `:107-108` | `:109` | `:111` | `:1900` / `:2041` |
|
|
99
|
+
| `hive/orch.py`(编排器 worker) | `:56` | `:64` | `:65-66` | `:67` | `:69` | `:705` / `:800` |
|
|
100
|
+
| `hive/serve_start.py`(serve 拉起) | `:47` | `:55` | `:56-57` | `:58` | `:60` | `:668` / `:691` |
|
|
101
|
+
| `md_cg/run_tests.py`(包内全量入口) | `:42` | `:50` | `:51-52` | `:53` | `:55` | `:166` / `:227` |
|
|
102
|
+
| `scripts/run_tests.py`(源码树全量入口) | `:34` | `:41` | `:42-43` | `:44` | `:46` | `:177` / `:240` |
|
|
103
|
+
|
|
104
|
+
接线不变式:调用锚均**早于该文件首个模块级 `def`**,故也早于一切文件/库 I/O
|
|
105
|
+
(`hive/hive_mcp/mcp_server.py:54-55` 的注释点名了本文件模块级读 `package.json` 的
|
|
106
|
+
`_package_version()` 就在锚之后)。「注释锚」列的区间=该入口的分隔注释行 + 8 行 `约束` 注释块;
|
|
107
|
+
以 `hive/hive_mcp/mcp_server.py`(本报告作者逐行读出的原文)为例:
|
|
108
|
+
`:50` `# ---------------------------------------------------------------- 入口自保证 UTF-8`,
|
|
109
|
+
`:51` `# 约束(工作纪律第 15 条):本调用必须在**任何文件/库 I/O 之前**——utf8_boot.ensure_utf8`。
|
|
110
|
+
其余六个入口的「注释锚」列只给该 `约束` 块的首行(分隔注释行即其上一行)。
|
|
111
|
+
|
|
112
|
+
**与既有的进程内守卫并存(两层各守一段)**:两个 MCP 入口 issue #39 的 `_force_utf8_stdio`
|
|
113
|
+
**原样保留**,仍是 `main()` 首行调用——`hive/hive_mcp/mcp_server.py:921` 定义 / `:942 def main()` /
|
|
114
|
+
`:943 _force_utf8_stdio()`;`md_cg/mcp_server.py:3694` 定义 / `:3837 def main()` / `:3838` 调用。
|
|
115
|
+
助手另加**进程级**保证(未开 utf8 模式则重启)与**后代继承面**(env),且助手内**不做**
|
|
116
|
+
stdio reconfigure(避免被 import 时产生进程级 stdio 副作用)。
|
|
117
|
+
|
|
118
|
+
### 1.2 B · 文本模式 `open` 的机械守卫与订正
|
|
119
|
+
|
|
120
|
+
**定义**:`md_cg/` `hive/` `scripts/` 下**全部** `.py`(含 `test_`/`bench_` 面)里,凡**文本模式**
|
|
121
|
+
的文件读写一律显式声明 `encoding=`;判据由 **AST** 承担,只认**内建 `open`**。
|
|
122
|
+
|
|
123
|
+
**守卫站点**([scripts/verify_open_encoding.py](../../scripts/verify_open_encoding.py),482 行 / 24 253 B / 无 BOM【本次】):
|
|
124
|
+
|
|
125
|
+
| 落点 | 行 | 内容 |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| 判据 ①–④(docstring) | `:14-23` | ①只认 `ast.Name` 且 `id=='open'`;②字面 mode 含 `b` ⇒ 不计;③mode 缺失/非字面量/`**kwargs` ⇒ **计入**(保守:无法确认即不合格);④`Path.read_text`/`write_text` 无 `encoding` ⇒ 计入,且点名 `write_text(p,text)` 形态的模块内自建同名函数**不是**本判据对象 |
|
|
128
|
+
| 扫描面 | `SCAN_DIRS = ("md_cg","hive","scripts")` `:59` | 三目录 |
|
|
129
|
+
| 白名单 | `WHITELIST: dict[str,str] = {}` `:63` | **空**;将来填入须双向报出,不留静默豁免 |
|
|
130
|
+
| ANCHOR 三类 | docstring `:25-35` | 判据锚(`_ANCHOR_CORPUS` 22 片段×预期违例数,`:99-…`)/扫描面锚(`_FLOORS` 下限 `:69-75`)/对照点位锚(`_ATTR_CONTROL_FILES` 5 文件 `:78-84` + `_ATTR_CONTROL_POINTS` 5 点位 `:90-96`) |
|
|
131
|
+
| 退出码 | `:40` | 0 全绿/1 有违例(含白名单不吻合)/2 ANCHOR-MISS |
|
|
132
|
+
|
|
133
|
+
**存量订正的 9 处真实站点**(本报告作者逐行读出,现树全部已带 `encoding="utf-8"`【本次】;
|
|
134
|
+
括号内为 HEAD 行号,`hive/hive_mcp/mcp_server.py` 因 A 路插入锚块下移 20 行):
|
|
135
|
+
|
|
136
|
+
| # | 现树站点 | HEAD 行号 | 形态 |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| 1 | `md_cg/test_n199_tokens_concurrent_write.py:157` | `:157` | `open(barrier, "w", encoding="utf-8").close() # 对齐放行` |
|
|
139
|
+
| 2 | `md_cg/test_security_audit_v21.py:227` | `:227` | `open(p, "w", encoding="utf-8").close()`(上下文 `:226 p = os.path.join(base, fn)`) |
|
|
140
|
+
| 3 | `md_cg/test_security_audit_v21.py:231` | `:231` | 同上(`:232` 断言「不误伤」) |
|
|
141
|
+
| 4 | `hive/test_n144_exec_cmd_terminal_race.py:71`(折行至 `:72`) | `:71` | `hold = open(os.path.join(d1, "result.json.tmp"), "w", encoding="utf-8")` |
|
|
142
|
+
| 5 | `hive/test_n144_exec_cmd_terminal_race.py:117` | `:116` | `hold3 = open(os.path.join(d3, "result.json.tmp"), "w", encoding="utf-8")` |
|
|
143
|
+
| 6 | `hive/test_stop_tree_kill.py:127` | `:127` | `with open(cp, encoding="utf-8") as f:`(mode 缺失,默认 `'r'`) |
|
|
144
|
+
| 7 | `md_cg/test_lock.py:22` | `:22` | `with open(TARGET, encoding="utf-8") as f:`(mode 缺失) |
|
|
145
|
+
| 8 | `md_cg/test_lock.py:38` | `:38` | `with open(TARGET, encoding="utf-8") as f:`(mode 缺失) |
|
|
146
|
+
| 9 | `hive/hive_mcp/mcp_server.py:673` | `:653` | `open(flag, "w", encoding="utf-8").close()`(kill 标志文件;注释 `:662-663` 明写「仍显式声明 encoding —— 判据『凡文本模式一律显式』(B 项,零例外)」) |
|
|
147
|
+
|
|
148
|
+
即 6 处「字面 mode 文本 open」+ 3 处「mode 缺失」,与契约清单的 **8+3** 相比,
|
|
149
|
+
差额 2 处见 §6.1(判为误分类,未改)。
|
|
150
|
+
|
|
151
|
+
### 1.3 助手分支的编号对照(§1.1 的 ②/③/F6 ↔ §3.1 的路 0–3′)
|
|
152
|
+
|
|
153
|
+
**为什么加这一节**:同一段 `ensure_utf8` 体在两处用了两套编号、两种排序——
|
|
154
|
+
§1.1 的表按**代码执行顺序**标注(②=utf8 模式已开早退,③=fail-fast),
|
|
155
|
+
§3.1 的表按助手**模块 docstring `:25-36` 的既有编号**(路 0=被 import、路 1=utf8 已真…),
|
|
156
|
+
而 docstring 把「被 import」列为第 0 路,与代码里「被 import」判断排在 utf8 判断**之后**相反。
|
|
157
|
+
下表把两套编号与代码行一次对齐(行号=`utf8_boot.py` 当前态,本报告作者读码所见):
|
|
158
|
+
|
|
159
|
+
| 代码位置(按执行顺序) | 代码内容 | §1.1 表的标号 | §3.1 表的「路」 | 契约条款编号 |
|
|
160
|
+
|---|---|---|---|---|
|
|
161
|
+
| `:218` | `_set_child_env()`——**最先执行**,无分支 | 「置子进程继承面」行 | 「全部」行 | 契约④前半 |
|
|
162
|
+
| `:219-220` | `if sys.flags.utf8_mode: return` | ② | **路 1** | 契约②前半(+同族「已开不重启」) |
|
|
163
|
+
| `:221-222` | `if not _caller_is_process_entry(): return` | F6 | **路 0** | 非契约原文(第一轮复核实测新增) |
|
|
164
|
+
| `:224-226` | `NO_REEXEC` 真 ⇒ 指引 + `SystemExit(2)` | ③ | **路 3** | 契约③ |
|
|
165
|
+
| `:227`+`:231-237` | `reexec_argv()` → `subprocess.run(三流)` → `SystemExit(rc)` | 「重启」行 | **路 2** | 契约②后半 +「必须显式传三流」的 must |
|
|
166
|
+
| `:228-230` | `argv is None`(`-c`/`-`)⇒ 指引 + `SystemExit(3)` | 「重启」行末 | **路 3′** | 契约外偏离(已在回报中声明) |
|
|
167
|
+
|
|
168
|
+
一句话记法:**§1.1 按代码从上到下数;§3.1 按 docstring 的路号数;两套只差「被 import」与「utf8 已开」的先后。**
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 2. 修前现场(逐条 payload / 观察 / 站点)
|
|
173
|
+
|
|
174
|
+
> 标记:`【采集】`= 工作流采集的批内实跑读数(可直接引用);`【本次】`= 本报告作者本次亲自
|
|
175
|
+
> 复现(命令与输出见 §4.5)。两列都有的条目表示同一事实被两条独立路径取到。
|
|
176
|
+
|
|
177
|
+
### 2.1 A 侧修前现场
|
|
178
|
+
|
|
179
|
+
| # | payload(怎么取) | 观察(读数) | 站点 | 标记 |
|
|
180
|
+
|---|---|---|---|---|
|
|
181
|
+
| A1 | 本机解释器直调 `locale.getlocale()`(读 Windows locale,不受 utf8 模式污染) | `('Chinese (Simplified)_China', '936')`;`sys.flags.utf8_mode=1`。clean-env 子进程(清 `PYTHONUTF8`/`PYTHONIOENCODING`、无 `-X utf8`):`utf8_mode=0`、`getpreferredencoding_False="cp936"`、`stdout/stdin/stderr="gbk"`、`stdin_errors="surrogateescape"`;**两种情形 `sys.getdefaultencoding()` 恒为 `'utf-8'`** ⇒ 裸奔面是文本 I/O 层而非 str↔bytes 层 | Python 3.12.10(MSC v.1943 64bit);本机解释器 | 【采集】【本次】读数逐字一致 |
|
|
182
|
+
| A2 | clean env 子进程里 `open(path,"w").write("中文復原テスト 😀")` | 缺省编码取 locale:`getpreferredencoding(False)="cp936"`,写盘抛 `UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f600' …`;**另一形态(纯中文、无 emoji)静默落 GBK 字节且 rc=0**——本次复现:裸 `open(p,"w").write("中文标题")` 落 `b'\xd6\xd0\xce\xc4\xb1\xea\xcc\xe2'`,rc=0、无异常,再用 utf-8 读回 `UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0`;加 `-X utf8` 则落 `b'\xe4\xb8\xad\xe6\x96\x87\xe6\xa0\x87\xe9\xa2\x98'`、读回 OK | Python stdlib 语义(探针在 `%TEMP%`) | 【采集】【本次】 |
|
|
183
|
+
| A3 | `os.environ` 直读 | `PYTHONUTF8='1'`、`PYTHONIOENCODING='utf-8'`、`PYTHONLEGACYWINDOWSSTDIO=None`、`LANG='C.UTF-8'`;本进程 `utf8_mode=1` ⇒ **本机没炸的第一层原因是环境变量**,正确性挂在环境上 | 会话环境 | 【采集】【本次】 |
|
|
184
|
+
| A4 | 修前树(`git archive HEAD` 导出)逐站点定位「三处入口自己钉的站点」 | ① `scripts/run_tests.py:88` `env = dict(os.environ, PYTHONUTF8="1", PYTHONIOENCODING="utf-8")`——**只对子进程生效**(`:81-82` 子进程 argv 另带 `-X utf8`);② `scripts/linux_verify.sh:9` `export PYTHONUTF8=1`;③ `src/lib/mdcg_client.ts:117-118` `PYTHONIOENCODING:'utf-8'` / `PYTHONUTF8:'1'`(`:98-102` 注释明写各自作用面)。三处**都是给别人的**,入口自身无保证 | `scripts/run_tests.py:88` · `scripts/linux_verify.sh:9` · `src/lib/mdcg_client.ts:117-118` | 【采集】 |
|
|
185
|
+
| A5 | clean env 启动修前树的 stdio MCP 入口 `python -m hive.hive_mcp.mcp_server`,喂 UTF-8 的 initialize+tools/list | rc=0;stdout 4 893 B 合法 UTF-8(含「灵枢」三字节 `\xe7\x81\xb5\xe6\x9e\xa2`),`as gbk: NO`;stderr 0 B ⇒ **修前树上这两个 MCP 入口的 stdio 面确已绿**(原因=issue #39 的进程内守卫),本批要补的是**进程级保证与后代传播**,不是这两条的 stdio 面 | `hive/hive_mcp/mcp_server.py`(修前树 `:901` 定义 / `:923 main()` 首行调用) | 【采集】 |
|
|
186
|
+
| A6 | 对照:停用进程内守卫(`M._force_utf8_stdio = lambda: None; M.main()`),其余不变 | rc=0(**不抛异常**);stdout 4 151 B,中文 description 落 `\xc1\xe9\xca\xe0\xb7\xe4\xb3\xb2…`=「灵枢」的 GBK;`as utf-8: NO ('utf-8' codec can't decode byte 0xc1 in position 249)`、`as gbk: YES`。⇒ 后果形态 = **静默产出非 UTF-8 字节**(rc=0、无错误帧、宿主按 UTF-8 读即乱码) | `hive/hive_mcp/mcp_server.py:922 main()`(对照态) | 【采集】 |
|
|
187
|
+
| A7 | clean env 子进程从管道读一行 UTF-8 中文,按 `sys.stdin` 缺省解码并回读 | `stdin_encoding="gbk"`、`stdin_errors="surrogateescape"`;解码得含孤立代理串 `'\udcad文復原テスト'`,该串再 `encode('utf-8')` 抛 `UnicodeEncodeError: … 'udcad' … surrogates not allowed`。`surrogateescape` 使坏数据**不报错**进入内存 ⇒ 读侧静默面;这正是 `_force_utf8_stdio` docstring 描述的崩溃机制(读侧解出 `\udcXX` → 写侧 `ensure_ascii=False` 编码炸) | Python stdio 缺省 | 【采集】 |
|
|
188
|
+
| A8 | 修前树 clean env 跑**五个无守卫入口** | ① `python -m md_cg.run_tests --list` rc=0、stdout 10 446 B、`as utf-8: NO (byte 0xb9 @10435)` / `as gbk: YES`;② `python scripts/run_tests.py --list` 逐字节相同;③ `python -m hive.serve_start --show-config` rc=1、stdout 1 930 B 含 `\xa3\xa8`(GBK 的「(」);④ `python -m hive.exec --help`、⑤ `python -m hive.orch --help` rc=1,走 `read_spec` 抛 `FileNotFoundError`(无 `job_dir`,只读无副作用),二者模块内 UTF-8 关键词命中数 0 ⇒ **五个入口自身零自保证,直接走 GBK** | `md_cg/run_tests.py` · `scripts/run_tests.py` · `hive/serve_start.py` · `hive/exec.py` · `hive/orch.py` | 【采集】 |
|
|
189
|
+
| A9 | 七个入口逐个机械审计(UTF-8 关键词命中 + `def main`/`__main__` guard 定位,修前树) | ① `hive/hive_mcp/mcp_server.py` 命中 8 ⇒ **有**(`:901`/`:923`);② `md_cg/mcp_server.py` 命中 8 ⇒ **有**(`:3677`/`:3821`);③ `hive/exec.py` 命中 **0** ⇒ 无;④ `hive/orch.py` **0** ⇒ 无;⑤ `hive/serve_start.py` **0** ⇒ 无;⑥ `md_cg/run_tests.py` 命中 1(`:81` 的 `env=dict(...)`,**只给子进程**)⇒ 自身无;⑦ `scripts/run_tests.py` 命中 2(`:88` 同形)⇒ 自身无 | 修前树七入口 | 【采集】 |
|
|
190
|
+
| A10 | clean env 子进程 `importlib.import_module(目标)` 后对 `sys.modules` 做差集 | `import md_cg` → 新增 `md_cg` + 15 个 `md_cg.*` = **16 个模块**(源于 `md_cg/__init__.py:2` 的 `from .mdcg import …` 重包);`import hive.hive_mcp.mcp_server` → 总数 71、**md_cg 不在 `sys.modules`**;`import hive.exec` → 139,亦无 md_cg(延迟 import)⇒ 「为一次 UTF-8 检查去 import md_cg」的代价成立,助手必须放仓根 | `md_cg/__init__.py:2` | 【采集】 |
|
|
191
|
+
| A11 | 读修前树 `hive/exec.py::_md_cg_import` 全 body(sys.path 插入先例) | `:854 def _md_cg_import():`;`:856` 读 `MDCG_HOME`;`:857-858` 空则取 `dirname(dirname(abspath(__file__)))`(=仓库根);`:859-860 if home not in sys.path: sys.path.insert(0, home)`;`:861-863` 再 import;调用点 `:911`。`hive/orch.py:374-375`、`:764` 同为延迟 import ⇒ 最小形态 = 「取 home → 不在 `sys.path` 则 `insert(0)`」 | `hive/exec.py:854-864` · `hive/orch.py:374-375,764` | 【采集】 |
|
|
192
|
+
| A12 | 父进程 clean env + `sys.stdout.reconfigure(encoding='utf-8')`,分两态派生子进程 | 仅 reconfigure:父 `stdout.encoding=utf-8` 但子进程 `utf8_mode=0 / getpreferredencoding=cp936 / stdout=gbk / env 两个皆 None`;再加 `os.environ['PYTHONUTF8']='1'`+`['PYTHONIOENCODING']='utf-8'`:子进程 `utf8_mode=1 / utf-8 / utf-8` ⇒ **reconfigure 只覆盖本进程三条流、不传播到后代;env 才传播** —— 这就是契约④与助手「无论走哪条路都置 env」的实测依据 | Python stdio/env 语义 | 【采集】 |
|
|
193
|
+
| A13 | 清空两 env、`MDCG_ROOT=临时库根`,`python -m md_cg.mcp_server` 喂 initialize/tools/list | rc=3、stdout 0 B;stderr 451 B **令牌闸 fail-closed**(`[mdcg-mcp] 令牌校验失败:令牌不存在… 拒绝启动(fail-closed)`)⇒ 该入口的 stdio E2E 在临时根上**未取得本人实跑读数**(诚实缺失);其守卫存在性由 A9 与 `--show-config` 代跑支撑。带 `-X utf8` 对照同样 rc=3、stderr 逐字节相同(非编码差异) | `md_cg/mcp_server.py` main() 起的令牌闸 | 【采集】(复现=false) |
|
|
194
|
+
| A13′ | **前置更正(第 2 版新增)**:同一入口为什么在别处是 rc=0——用伪造字面量 `MDCG_TOKEN=<dummy>`(不抄录任何真实凭据)+同一临时根实跑;再把 `MDCG_*` 一并清空重跑同一命令 | 带失效 `MDCG_TOKEN`:rc=3、stdout 0 B、stderr 同形(`令牌校验失败:令牌格式非法(应为 mdcg1.<role>.<token_id>.<secret>)…拒绝启动(fail-closed)`);`MDCG_*` 清空后:rc=0、`serverInfo.name=mdcg-mcp` ⇒ **A13 的失败根因是「会话 env 里带着失效 `MDCG_TOKEN` 而那次只清了两 env」**,三个读数(A13 / E2E-MDCG / 复核)是**三种前置**而非同一前置下的矛盾(完整对照见 §5.1 前置对齐) | `md_cg/mcp_server.py` main() 起的令牌闸 | 【本次】 |
|
|
195
|
+
| A14 | 全部实验树来自 `git archive HEAD` 解包到系统临时目录;探针写在 `%TEMP%` | 临时根 `%TEMP%\lingshu_repro_A`(2 076 件、`utf8_boot.py present? False` 证明是修前树);实验前后 `git status --porcelain` 逐字一致(批内读数 14 M + 5 ??;**注**:本报告作者本次读为 14 M + 6 ??,差额即本报告文件本身)⇒ 未编辑工作区、未触碰在役数据根、未重建/重启 hive serve、未 `git add/commit/push` | `D:\program\dsh-memory-main` @ `174cd25f…` | 【采集】【本次】我另建的 HEAD 物化树同判(`utf8_boot.py` 不在) |
|
|
196
|
+
|
|
197
|
+
### 2.2 B 侧修前现场
|
|
198
|
+
|
|
199
|
+
| # | payload(怎么取) | 观察(读数) | 站点 | 标记 |
|
|
200
|
+
|---|---|---|---|---|
|
|
201
|
+
| B1 | 自写 AST 扫描器重算「文本模式裸 `open`」(只读;脚本落 `%TEMP%`) | `builtin open total: 913`;`by class: {'text_missing_mode': 397, 'text_literal': 427, 'binary': 89}`;`GAPS (text-mode builtin open w/o encoding): 9`;`read_text/write_text w/o encoding: 0`。扫描面 486 个 `.py`,0 个读失败。清单核对(**第 2 版已改措辞**):在**已声明** `encoding` 的文本字面 mode 调用里,`encoding` 取值集合**只有** `{'utf-8'}`(无第二种编码值)⇒ 本仓文本面不存在「用别的编码」这第二类问题;但**缺口数 9 本身仍由 AST 逐点判定**,此句只是清单核对,**不构成对 9 的独立证明**(未声明 `encoding` 的那 6 处字面 mode 调用不进这个集合——它们恰恰就是缺口)。`text_nonliteral` = 0 | 修前树(HEAD 物化) | 【采集】【本次】我自写的扫描器复现(同一 HEAD 物化树):486 文件 / `text_literal` **427** + `mode_missing` **397** = 文本 **824** / `binary` **89** / 缺口 **9**(427+397+89 = **913**,与左列 `builtin open total: 913` 同数)。**工作树侧另为** 489 文件 / `text_literal` **437** + `mode_missing` **407** = 文本 **844** / `binary` **92** / 缺口 **0**(437+407+92 = 936)。行号见 §1.2 表 |
|
|
202
|
+
| B2 | 逐点读源码行——6 处「字面 mode 文本 open」 | 全部为文本模式且无 `encoding`(现场原文见 §1.2 对照):`md_cg/test_n199_tokens_concurrent_write.py:157`、`md_cg/test_security_audit_v21.py:227/:231`、`hive/test_n144_exec_cmd_terminal_race.py:71/:116`、`hive/hive_mcp/mcp_server.py:653`。六处均为「空标记文件」语义(无正文写出),但按「凡文本模式一律显式」的统一规则确应订正 | 见左列(HEAD 行号) | 【采集】【本次】读现树行:均已带 `encoding="utf-8"` |
|
|
203
|
+
| B3 | 逐行读 `scripts/_mdcg_reindex_dshlogs.py:148-151` 与两处调用点 `:365/:411` | `:148 def write_text(p: str, text: str) -> None:`、`:150 with open(p, "w", encoding="utf-8", newline="\n") as f:` —— **编码早已显式且额外锁 LF**;`grep -n write_text` 该文件仅 3 行(148 def、365、411)。**后果级铁证**:把 helper 源码逐字复制到临时面实测 `write_text(tgt,'x',encoding='utf-8')` → `TypeError: write_text() got an unexpected keyword argument 'encoding'` ⇒ 机械照做契约①会**直接打断该脚本**。故这两点**不是缺口**,正确订正点数是 **9** | `scripts/_mdcg_reindex_dshlogs.py:148-151`(helper)、`:365`、`:411` | 【采集】【本次】我读出 `:147` 的生效条件注释与 `:150` 的 `encoding="utf-8", newline="\n"` 逐字一致 |
|
|
204
|
+
| B4 | 同一文件的「下游闭环」核查 | 写完的三处读点**全是显式 utf-8**:同文件 `:178-180 def _lines(p): with open(p, "r", encoding="utf-8")`、`md_cg/docindex.py:472`、`md_cg/refindex.py:170/:495`。**反事实实测**(`PYTHONUTF8=0`,`getpreferredencoding(False)=cp936`):裸 `open(p,'w').write('中文…')` 落 `b'# \xbb\xe1\xbb\xb0 abc \xa1\xa4 …\r'`(GBK + `\r\n` 翻译),用 utf-8 读回 `UnicodeDecodeError`;换成 `:150` 的写法则落 `b'\xe4\xbc\x9a\xe8\xaf\x9d…'` 读回正常 ⇒ 若这两点走默认编码,后果**不是静默乱码而是 `_lines(tpath)`/docindex 硬失败**,且字节不稳定会连带打破对账的逐字节幂等 | 同上 + `md_cg/docindex.py:472` · `md_cg/refindex.py:170,495` | 【采集】 |
|
|
205
|
+
| B5 | 3 处「mode 缺失」需判定点 | `md_cg/test_lock.py:22`(`:17-23`:`FileLock(TARGET)` 内 `int(f.read().strip() or 0)`)、`:38`(`:33-39`:6 子进程 `wait()` 后 `int(f.read().strip())`)、`hive/test_stop_tree_kill.py:127`(`:125 cp = os.path.join(TMP,"childpid.txt")`、`:128 child_pid = int(f.read().strip())`)——三处都是纯 ASCII 数字读,**无实际风险**,但契约②的保守判据(mode 缺失即计入)要求显式化 | `md_cg/test_lock.py:22,38` · `hive/test_stop_tree_kill.py:127` | 【采集】【本次】 |
|
|
206
|
+
| B6 | 89 处二进制为什么**不该**加 encoding | 计数复现:AST 扫描独立得出 `binary count: 89`。对照实测(真跑):`open(p,'wb'/'ab'/'rb',encoding='utf-8')` 三个模式**全部**抛 `ValueError: binary mode doesn't take an encoding argument` ⇒ 给这 89 处加 `encoding=` 不是「遗漏」而是**非法**。仓库内实例:`hive/exec.py:350 with open(path, "rb") as f:`(`:351` 读 32 字节做 PNG/JPEG/GIF/BMP/WEBP 魔数嗅探,纯字节比较);`hive/serve_start.py:474 logf = open(os.path.join(jobs,"_serve.log"), "ab")` | `hive/exec.py:350` · `hive/serve_start.py:474`(+扫描器 SUMMARY) | 【采集】【本次】我实测计数 89(HEAD) |
|
|
207
|
+
| B7 | Attribute 形态假阳性——按 `attr == 'open'` 匹配会误报哪些点 | 三目录内 Attribute 形 `*.open` 共 **66** 处 = `io.open` 62 / `tarfile.open` 2 / `os.open` 1 / `gzip.open` 1。未排除 Attribute 时多报 3 处(连二进制豁免也不加时 5 处)。逐点读源码确认:① `md_cg/fsutil.py:204 fd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_APPEND, 0o600)`(`:206 os.write(fd, line.encode("utf-8"))`)——第二实参是 int 标志位,`os.open` **没有 encoding 参数**;② `scripts/check_publish_artifact.py:696 with tarfile.open(fileobj=io.BytesIO(blob), mode="r:gz")`——mode `r:gz` 不含 `'b'`,会落进「文本模式」分支;③ `scripts/criteria_fingerprint.py:45 with tarfile.open(fileobj=io.BytesIO(r.stdout))`(无 mode);④ `md_cg/lexicon/build_cedict_en_zh.py:60/:62` 为 `io.open(RAW_GZ,"wb")` / `gzip.open(RAW_GZ,"rb")` 二进制流 ⇒ **只有 `ast.Name` 且 `id=='open'` 才安全**,Attribute 排除不是可选项 | `md_cg/fsutil.py:204` · `scripts/check_publish_artifact.py:696` · `scripts/criteria_fingerprint.py:45` · `md_cg/lexicon/build_cedict_en_zh.py:60,62` | 【采集】【本次】我自写 AST 扫描:工作树 `Attribute .open` = 66;其中**无 `encoding` 关键字**的恰好这 **5** 处;HEAD 树同为 66 |
|
|
208
|
+
| B8 | 守卫作用域充分性(额外查证) | 同一 AST 口径扩到**仓根全量**(跳过 `.git/__pycache__/node_modules/.venv/_md_cg_p0/.zcode`,去掉已单扫的三目录):`OUT-OF-SCOPE strict bare text opens: 0` ⇒ 三目录覆盖面**充分**。另查 `open` 名遮蔽:三目录内对 `open` 的赋值/海象/形参/`def`/`import from` 绑定共 **0** 处 ⇒ `ast.Name + id=='open'` 无遮蔽风险 | 全仓扫描输出;遮蔽统计 | 【采集】 |
|
|
209
|
+
| B9 | 「同一 AST 口径下逐点核对现树」 | 工作树:`binary=0 / text=9 / gap=0`(单文件复核口径,针对本批改动/复核的文件);`hive/hive_mcp/mcp_server.py` 的 kill 标志行由 `:671` 漂到 `:673`。**三个行号的归属(第 2 版补)**:`:653` = **HEAD 行号**(本报告作者在 HEAD 物化树上实测,正是 B1/§4.3 缺口清单里的那一行);`:673` = **工作树当前行号**(本报告作者读码所见,`A 路` 锚块插在该文件 `:50-65` 所致);`:671` = **批内记录里的一处中间读数**(原文只有这一句,无其时点说明)——本报告**未能取证**它对应哪个时刻(该文件未提交、无中间快照),因此**不为它背书**,也不把它当作第三个独立读数:可核的两个端点是 `:653`→`:673` | 见 §1.2 表 | 【采集】【本次】 |
|
|
210
|
+
| B10 | 并发写者与快照边界 | 会话期间另有并发写者(mtime 21:25–21:26 命中 A 路七个入口;`hive/jobs/_serve.json` 21:28),故计数在会话内漂移(文本 840→844、二进制 89→91);全部读数为**对应时刻快照**。我改动/复核的文件均 UTF-8 无 BOM | 工作区 | 【采集】(B 路现状计数以本次读数 844/92 为准,见 §6.1) |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 3. 修法契约与落点
|
|
215
|
+
|
|
216
|
+
### 3.1 A 路
|
|
217
|
+
|
|
218
|
+
**助手三路语义**(`utf8_boot.py` 模块 docstring `:25-36` + `ensure_utf8` docstring `:202-216`):
|
|
219
|
+
下表的「路 0–3′」是助手 docstring 的既有编号(**不是**代码执行顺序——代码里「被 import」排在
|
|
220
|
+
「utf8 已开」之后);**与 §1.1 表(按代码顺序标注)的逐项对照见 §1.3**。
|
|
221
|
+
|
|
222
|
+
| 路 | 条件 | 行为 | 落点 |
|
|
223
|
+
|---|---|---|---|
|
|
224
|
+
| 0 | 调用方**不是**进程入口(被 import) | 只置 env,立即返回——**绝不重启/退出/打日志** | `:221-222` |
|
|
225
|
+
| 1 | `sys.flags.utf8_mode` 真 | 立即返回(不重启、不打日志) | `:219-220` |
|
|
226
|
+
| 2 | 未开 且 `LINGSHU_UTF8_NO_REEXEC` 非真 | `reexec_argv()` → `subprocess.run(..., stdin/stdout/stderr=sys.std*)` → `SystemExit(proc.returncode)`;重启后子进程 `utf8_mode` 已开 ⇒ 结构上**不可能二次重启** | `:227-237` |
|
|
227
|
+
| 3 | 未开 且 `NO_REEXEC` 真 | 打印**可执行指引**以 `2` 退出(不能接受子进程的场景:嵌入式宿主/CI 调试/进程树审计) | `:224-226` |
|
|
228
|
+
| 3′ | `-c`/`-` 形态(源码不可重放) | 不重启,指引 + `3` 退出 | `:228-230` |
|
|
229
|
+
| 全部 | — | 先 `_set_child_env()` 置 `PYTHONUTF8=1`/`PYTHONIOENCODING=utf-8`,供随后派生的子进程继承 | `:218` |
|
|
230
|
+
|
|
231
|
+
**第一轮三缺口的修法**(契约外的复核发现,均已修并纳入守卫):
|
|
232
|
+
|
|
233
|
+
- **F2(指引必须可照抄)**:新增单点判别 `_m_module_name()`,`reexec_argv()` 与 `launch_hint()` **共用**;
|
|
234
|
+
`-m` 入口的指引印 `python -X utf8 -m md_cg.mcp_server` / `-m hive.hive_mcp.mcp_server`,
|
|
235
|
+
其余形态印入口路径。反例:`python -X utf8 md_cg/mcp_server.py` → rc=1
|
|
236
|
+
`ImportError: attempted relative import with no known parent package`(`md_cg/mcp_server.py:4038` 一带的相对 import)。
|
|
237
|
+
- **F6(被 import 不得重启)**:新增 `_caller_is_process_entry()`(`f_globals` 与 `__main__.__dict__`
|
|
238
|
+
**同一性**判定)。实现上**不改调用点**——七入口仍是 `ensure_utf8(__file__)`,故守卫的调用锚一字不动。
|
|
239
|
+
复核实测(未加判据前):单会话解释器启动 2 次、第 3 条请求被吞、**无错误帧、rc=0**。
|
|
240
|
+
- **F3(三流显式传递必须有判据)**:契约字面要求「在『助手不传三流』的临时副本上跑同一条 E2E,
|
|
241
|
+
组帧必须转红」**按字面做不到**——宿主 `Popen(stdin=PIPE, stdout=PIPE, stderr=PIPE)` 时,
|
|
242
|
+
子进程即使漏传三流也会**直接继承父进程的 fd 0/1/2**(同一条管道),组帧照样成立(实测:单独剥离任一条流,
|
|
243
|
+
E2E-HIVE/E2E-MDCG/ONCE/NOOP 四项全绿)。故判据拓扑换成**继承无法掩盖**的那一种:父进程先把三条流
|
|
244
|
+
换成**普通文件对象**(非 fd 0/1/2),并置 env 标记 `UTF8GUARD_STREAMS_ONCE` 供重启出的子进程继承,
|
|
245
|
+
堵死「子进程自己重开文件」的自愈路径;此时「显式传递 ⇒ 写回显式传入的流」与「漏传 ⇒ 写进宿主管道」
|
|
246
|
+
可观测、可定点变异,且判据反证宿主管道零痕迹。
|
|
247
|
+
|
|
248
|
+
**探针熔断(额外加固)**:`GUIDE` 的「采指引」在变异态下会**真跑**入口(实测
|
|
249
|
+
`scripts/run_tests.py` 会真跑整个套件并拉起守卫自身,递归跑套件、孤儿进程堆积,一次跑挂 10 分钟以上);
|
|
250
|
+
「探针」在幂等被破坏时会无限自重启。修法:采指引与探针各挂**独立计数熔断**
|
|
251
|
+
(`sitecustomize` 在第 N 次解释器启动 `os._exit(97)`;`FUSE_LIMIT=6` `:158`),各用独立计数文件,
|
|
252
|
+
采指引超时收到 60s。
|
|
253
|
+
|
|
254
|
+
**与既有层的关系**:两个 MCP 入口的 `_force_utf8_stdio`(issue #39)**并存**,`main()` 首行仍调
|
|
255
|
+
(`hive/hive_mcp/mcp_server.py:943`、`md_cg/mcp_server.py:3838`);两层判据分属
|
|
256
|
+
`md_cg/test_issue39_utf8_stdio.py` 与本守卫。助手内**不做** stdio reconfigure(契约里那是「可选」)。
|
|
257
|
+
|
|
258
|
+
**未触碰**:未改任一入口的业务语义与参数面;未动 `scripts/run_tests.py` 既有的「给子进程覆盖 env」那段。
|
|
259
|
+
|
|
260
|
+
### 3.2 B 路
|
|
261
|
+
|
|
262
|
+
- **守卫判据**= §1.2 的 ①–④(AST 只认内建 `open`;二进制豁免;mode 缺失/非字面量/`**kwargs` 计入;
|
|
263
|
+
`read_text`/`write_text` 计入且不误伤模块内自建同名函数)。
|
|
264
|
+
- **ANCHOR 三层 fail-closed**(漂移即 `2`,不当假绿):判据锚(22 片段×预期违例数)、扫描面下限
|
|
265
|
+
(`_FLOORS`:文件 420 / 文本 700 / 二进制 76 / Attribute 56 / `read_text-write_text` 4,
|
|
266
|
+
取实测基线 ~85%)、对照点位锚(5 文件 + **5 点位**:`check_publish_artifact.py/tarfile.open`、
|
|
267
|
+
`criteria_fingerprint.py/tarfile.open`、`fsutil.py/os.open`、`build_cedict_en_zh.py/io.open`、
|
|
268
|
+
`build_cedict_en_zh.py/gzip.open`——文件级锚只保证「文件里还有某个 Attribute `.open`」,
|
|
269
|
+
**点位级锚才保证「被误报的那一类形态仍在对照面上」**)。
|
|
270
|
+
- **白名单为空**(`WHITELIST={}` `:63`),双向报出逻辑未改,不留静默豁免通道。
|
|
271
|
+
- **存量订正**:9 处(§1.2 表),全部仅加 `encoding="utf-8"`;未加白名单、未改任何 assert、
|
|
272
|
+
未动任何二进制模式 `open`。
|
|
273
|
+
- **本批对守卫自身的两处改动**:① 修 `docstring` 文档漂移(判据锚片段数「20 个」→「22 个」,
|
|
274
|
+
与 `_ANCHOR_CORPUS` 22 片段实测一致);② 新增点位级假阳性对照锚 `_ATTR_CONTROL_POINTS` +
|
|
275
|
+
`attr_open_callees()`,`--self-test` 增报「契约点名 Attribute 假阳性点位 5/5 逐个仍在位」;
|
|
276
|
+
③ `scripts/test_verify_open_encoding.py` 新增 ④e 反证(给点位锚补一个 ghost 形态必须 rc=2 且
|
|
277
|
+
stdout 含「对照点位漂移」),`assert` 数 24→26。
|
|
278
|
+
|
|
279
|
+
**已知未接**:守卫**未接** `utf8_boot.ensure_utf8`(设计稿 §7.3 D2 的已知项)——B 契约未要求,
|
|
280
|
+
且 `scripts/test_verify_open_encoding.py` 会 import 该模块,模块级重启语义有副作用。
|
|
281
|
+
**第 2 版补实测后果(【本次】)**:clean env(清 `PYTHONUTF8`/`PYTHONIOENCODING`、不带 `-X utf8`)
|
|
282
|
+
下直接跑该守卫 → **rc=1**、stdout 72 B **非 UTF-8**(GBK 字节),stderr 为
|
|
283
|
+
`UnicodeEncodeError: 'gbk' codec can't encode character '\u2714'`,崩点在 `:476` 的 `print("✔ …")`
|
|
284
|
+
(`--self-test` 同因,崩点 `:444`);`scripts/test_verify_open_encoding.py` 在同 env 下 **rc=0、
|
|
285
|
+
但 stdout 同样非 UTF-8**(1166 B,首字节 `0xa2`)。即 `rc=1` 与守卫自己的「1 = 存在违例」**同码**,
|
|
286
|
+
在没设 env 的机器上会把「崩溃」读成「有违例」。是否接 `ensure_utf8` 属下一批判定(建议见 §7-Q7)。
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 4. 验证数字
|
|
291
|
+
|
|
292
|
+
### 4.1 A 守卫 `scripts/test_utf8_boot_guard.py`(1 254 行 / 66 870 B / 无 BOM)
|
|
293
|
+
|
|
294
|
+
判据面常量:`ENTRIES` 七入口 `:92-100`(顺序即报告顺序)、`ANCHOR_*` 四常量 `:102-105`、
|
|
295
|
+
`ENTRY_LAUNCH` 真实接入形态 `:110-132`、`IMPORT_CASES` `:136-144`、`_CHECKS` 11 条 `:862-874`、
|
|
296
|
+
`_MUTATIONS` 13 条 `:1030-1057`。
|
|
297
|
+
|
|
298
|
+
**断言数 = 11 条正向判据 + 13 项定点变异 = 24;通过 24;rc=0;耗时 21.8s**【本次】。
|
|
299
|
+
`--no-mutations` → 11/11、rc=0、1.3s【本次】。21.8s 远在 `scripts/run_tests.py` / `md_cg/run_tests.py`
|
|
300
|
+
的 `--timeout` 默认 900s 之内。
|
|
301
|
+
|
|
302
|
+
**正向判据逐条(本次实跑输出摘录)**
|
|
303
|
+
|
|
304
|
+
| id | 判据 | 读数 |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| E2E-HIVE | ① stdio MCP 端到端组帧(hive,清空 env 无 `-X utf8`) | rc=0、帧数 3、中文入参往返成功 |
|
|
307
|
+
| E2E-MDCG | ① 同上(md_cg) | rc=0、帧数 2、中文 `write` 被受理(ok) |
|
|
308
|
+
| ONCE | ② 只重启一次 | 启动计数恰 2(首启未开 → 重启一次后已开) |
|
|
309
|
+
| FAILFAST | ③ `LINGSHU_UTF8_NO_REEXEC` | rc=2 + 指引含 `-X utf8`/`PYTHONUTF8`/`LINGSHU_UTF8_NO_REEXEC`,启动 1 次(未重启) |
|
|
310
|
+
| NOOP | ④ `-X utf8` ⇒ 不重启 | 启动 1 次 |
|
|
311
|
+
| ENV-CHILD | ④ env 供后代继承 | 本进程 `utf8_mode=1` 且 env 已置;孙进程 `GRAND 1 utf-8` |
|
|
312
|
+
| ANCHOR | ⑤ 七入口锚点 | 7 个入口注释锚 + import 锚 + 调用锚齐备,且调用**早于首个 `def`** |
|
|
313
|
+
| DEPDIR | ⑥ 依赖方向 | 助手 import 面 = `['__future__','locale','os','subprocess','sys']`(纯 stdlib);import 后 `md_cg`/`hive`/`scripts` 均不在 `sys.modules` |
|
|
314
|
+
| GUIDE | ⑦ 指引可照抄(F2) | 七入口指引逐个**真跑通**:两 MCP 入口 `-m` 形态握手 `hive-mcp`/`mdcg-mcp`;`exec.py`/`orch.py` 见 usage(rc=2);`serve_start.py --show-config` stdout 见 `"keys"`(rc=1、2 349 B);两个 run_tests `--list` rc=0 且产物 10 531 B |
|
|
315
|
+
| IMPORT | ⑧ 被 import 不重启(F6) | 启动 1 次;七入口全部 import 成功(7 个标记齐备) |
|
|
316
|
+
| STREAMS | ⑨ 三流显式传递(F3) | 非 fd 0/1/2 拓扑下组帧往返成功:`echo='中文标题:编码守卫'`、`stderr` 标记落显式流、**宿主管道零痕迹** |
|
|
317
|
+
|
|
318
|
+
**定点变异 13 项:每处红项集与退出码**(本次实跑,逐项与预期**恰好**一致)
|
|
319
|
+
|
|
320
|
+
| 变异 | 做了什么 | 红项集 | 退出码 |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| M1-no-m-form | 重启 argv 丢掉 `-m` 模块语义(直跑文件) | `{E2E-MDCG}` | 1 |
|
|
323
|
+
| M2-early-return-inverted | utf8 已开也不早退(幂等被破坏) | `{ENV-CHILD, FAILFAST, GUIDE, NOOP, ONCE, STREAMS}` | 1 |
|
|
324
|
+
| M3-env-not-set | 第四项失效:不置两 env | `{ENV-CHILD}` | 1 |
|
|
325
|
+
| M4-no-reexec-ignored | 显式退出通道失效(`NO_REEXEC` 被忽略) | `{FAILFAST, GUIDE}` | 1 |
|
|
326
|
+
| M5-anchor-call-removed | 入口不再调用助手 | `{ANCHOR, GUIDE}` | **2**(ANCHOR-MISS) |
|
|
327
|
+
| M6-anchor-order-drift | 调用锚挪到首个 `def` 之后 | `{ANCHOR, GUIDE}` | 1 |
|
|
328
|
+
| M7-dep-direction | 助手反向 import `md_cg` | `{DEPDIR}` | 1 |
|
|
329
|
+
| **M8-green-control** | **无关改名(假阳性对照)** | **∅** | **0**(必须全绿) |
|
|
330
|
+
| M9-stream-stdin-dropped | 重启不传 `stdin=` | `{STREAMS}` | 1 |
|
|
331
|
+
| M10-stream-stdout-dropped | 重启不传 `stdout=` | `{STREAMS}` | 1 |
|
|
332
|
+
| M11-stream-stderr-dropped | 重启不传 `stderr=` | `{STREAMS}` | 1 |
|
|
333
|
+
| M12-guide-no-m-reuse | 指引不复用 `-m` 判别 | `{GUIDE}` | 1 |
|
|
334
|
+
| M13-import-gate-off | 被 import 也重启(F6 前行为) | `{IMPORT}` | 1 |
|
|
335
|
+
|
|
336
|
+
> 全套 13/13 =「红项集与退出码**恰好**等于各自预期」(对照项预期即 `∅`/`0`)。批内记录显示:
|
|
337
|
+
> 首轮 M6 实测多出 `GUIDE`、M9–M11 实测 `∅`(暴露 STREAMS 探针「子进程自愈」盲区),
|
|
338
|
+
> 两处都按现场改实现/改预期后才钉死——即预期值是**逐项实测写死**而非猜的。
|
|
339
|
+
|
|
340
|
+
**M1 为什么只红 `E2E-MDCG`(第 2 版补实测,回应 §5.1 uncovered 3)**:M1 关掉助手 `reexec_argv()` 的 `-m`
|
|
341
|
+
分支后,重启退化为「直跑文件」。本报告作者实测两种入口在该形态下的存活差异:
|
|
342
|
+
`python -X utf8 hive/hive_mcp/mcp_server.py` → **rc=0、initialize 握手 `hive-mcp` 成功**(该入口的锚点自带
|
|
343
|
+
`sys.path` 插入,且入口执行路径上没有相对 import);`python -X utf8 md_cg/mcp_server.py` → **rc=1**,
|
|
344
|
+
`File "md_cg/mcp_server.py", line 3850, in main / from .datapath import mdcg_root as _probe_root /
|
|
345
|
+
ImportError: attempted relative import with no known parent package`。⇒ **单点变异打不红 `E2E-HIVE`**:
|
|
346
|
+
hive 入口对「直跑文件」是免疫的;要把它打红必须**同时**破坏 hive 入口自身的某一层(例如同时停用
|
|
347
|
+
`ensure_utf8` 与 `_force_utf8_stdio`,即复核者的场外变体 B),而守卫的变异表当前是**单点注入**
|
|
348
|
+
(`_sub()` 要求目标片段恰出现一次,`scripts/test_utf8_boot_guard.py:905-907`)。补法建议见 §7-Q3。
|
|
349
|
+
|
|
350
|
+
**变异自证所跑的「临时物化面」规模**(第 2 版补,用于对上 §5.1 里的「1 158 文件」):守卫在注入变异前
|
|
351
|
+
把仓面物化到临时目录,其自跑输出原样为 `⑦ 定点变异自证(临时物化仓面 1158 文件,绝不改工作树)`。
|
|
352
|
+
1158 的口径 = `git ls-files -z --cached --others --exclude-standard`(追踪面 + 未追踪面)扣掉
|
|
353
|
+
`_MATERIALIZE_SKIP` 的 10 个前缀(`docs/`、`skills/`、`node_modules/`、`hive/target/`、`hive/jobs/`、
|
|
354
|
+
`hive/interop/`、`.git/`、`.zcode/`、`_md_cg_`、`rust/target/`,`scripts/test_utf8_boot_guard.py:177-179`;
|
|
355
|
+
清单构造见 `_materialize_list() :1062-1080`),**含全部文件类型**;本报告作者按同一口径复算 = **1158**(【本次】)。
|
|
356
|
+
它与 §4.3 的 486 / 489(只数 `md_cg/`+`hive/`+`scripts/` 三目录的 `*.py`)**不是同一口径**,两者不可互比。
|
|
357
|
+
|
|
358
|
+
### 4.2 B 守卫与回放断言
|
|
359
|
+
|
|
360
|
+
| 命令(本次实跑) | rc | 读数 |
|
|
361
|
+
|---|---|---|
|
|
362
|
+
| `python scripts/verify_open_encoding.py` | 0 | `扫描面:489 文件 / 文本 open 844 / 二进制 open 92 / Attribute .open 66`;`✔ 全部文本模式调用点均显式声明 encoding=` |
|
|
363
|
+
| `python scripts/verify_open_encoding.py --self-test` | 0 | 判据锚 **22/22** 片段预期成立;扫描面 **文件 489 / 文本 open 844(其中 mode 不可判定 407)/ 二进制 open 92 / Attribute `.open` 66 / `read_text`-`write_text` 6**(第 2 版已逐项加键:`--self-test` 比正跑多报末一项,故是五项;正跑的印字在 `:454-455`,只印前四项);真实仓裸文本违例 **0**;Attribute 对照 **5 个文件**全部核对;**契约点名 Attribute 假阳性点位 5/5 逐个仍在位**;定点变异 3 目标 × 4 变体 |
|
|
364
|
+
| `python scripts/test_verify_open_encoding.py` | 0 | **ALL PASS**;`assert` 计数 **26**(`docstring` 同步);③ 逐变体命中数:裸文本无 mode→1、`mode='w'`→1、mode 非字面量→1、`read_text` 无 encoding→1、二进制→0、`Attribute io.open`→0、`Attribute tarfile.open`→0、带 encoding→0;**③c 完整扫描面注入一处裸文本 open → exit 1 且恰好 1 处(行号 47)**;④a–④e 五条 ANCHOR-MISS 反证**全 rc=2**;⑤ 白名单双向 **rc=1**;⑥ `WHITELIST=={}` |
|
|
365
|
+
|
|
366
|
+
### 4.3 本报告作者自写的独立 AST 复核(两条独立路径交叉印证)
|
|
367
|
+
|
|
368
|
+
| 树 | 文件数 | 内建二进制 `open` | 文本模式 `open` | 文本裸 `open`(缺口) | Attribute `.open` 总数 / 其中无 `encoding` |
|
|
369
|
+
|---|---|---|---|---|---|
|
|
370
|
+
| HEAD 物化树(`git archive HEAD` → `%TEMP%\rpt_head_tree`,`utf8_boot.py` 不在) | 486 | **89** | 824(字面 427 + mode 缺失 397) | **9**(行号 = §1.2 表,逐点对得上) | 66 / **5**(=契约点名的 5 处) |
|
|
371
|
+
| 工作树 | 489 | 92(89 + 新增未追踪件 `scripts/test_utf8_boot_guard.py` 的 3 处) | 844(字面 437 + mode 缺失 407) | **0** | 66 / 5 |
|
|
372
|
+
|
|
373
|
+
⇒ **可比性说明(第 2 版订正,原句「两侧均与守卫自报一致」不成立)**:**只有工作树侧**能与「守卫自报」比——
|
|
374
|
+
守卫自报(489 文件 / 文本 844 / 二进制 92 / Attribute 66)与本报告作者的工作树扫描**逐项一致**;
|
|
375
|
+
**HEAD 侧没有守卫读数可比**(守卫只扫当前工作树,全文没有它在任何修前树上的输出),它的可比对象是
|
|
376
|
+
**批内另一路扫描器**(§2.2 B1 的 `【采集】` 读数:`builtin open total: 913`、`mode_missing 397`、
|
|
377
|
+
`text_literal 427`、`binary 89`、`GAPS 9`)——我的 HEAD 扫描与之**逐项一致**(427+397+89 = 913,
|
|
378
|
+
缺口 9 处且行号逐点对上)。`89 + 9 + 5 = 103` 的口径分解见 §6.1(并见其中「103 的两套分解不能互证」一段)。
|
|
379
|
+
|
|
380
|
+
### 4.4 python 全量 与 容器
|
|
381
|
+
|
|
382
|
+
| 面 | 命令 / 来源 | 读数 |
|
|
383
|
+
|---|---|---|
|
|
384
|
+
| python 全量【本次】 | `python scripts/run_tests.py`(清空被测面之外的默认 env、`PYTHONUTF8=1`) | **269/269 通过,5 跳过(依赖缺失/平台不符)**,rc=0,耗时 **101.7s**;含 `PASS scripts.test_utf8_boot_guard` 与 `PASS scripts.test_verify_open_encoding`(两条本批守卫在套件内可见可跑) |
|
|
385
|
+
| python 全量【本次·**clean env 复跑**】 | 同一命令,但 env 清空 `PYTHONUTF8`/`PYTHONIOENCODING`/`PYTHONLEGACYWINDOWSSTDIO`(不带 `-X utf8`) | **269/269 通过,5 跳过,rc=0,100.7s**;stdout 10 260 B **合法 UTF-8**(`decodes as utf-8: YES`)——runner 自身的 `ensure_utf8` 重启了自己,故整套在 cp936 locale 下仍全绿、且输出不是 GBK |
|
|
386
|
+
| 批内两失败模块 ×3(clean env) | `python -m md_cg.test_n212_n213_n224_generation_gates` 与 `python -m md_cg.test_p28_refcheck` 各 3 次 | 6/6 全 **rc=0**(n212 每次 `PASS=42 FAIL=0 LIVE=26`、0.3s;p28 3 次 rc=0、0.3–0.4s)⇒ 本机此刻**不复现**批内那两处失败 |
|
|
387
|
+
| python 全量【采集】 | 批内实跑记录 | 267/269 通过、5 跳过,**失败 2**:`md_cg.test_n212_n213_n224_generation_gates`、`md_cg.test_p28_refcheck` |
|
|
388
|
+
| 容器两栈【采集】 | 批内记录 | 退出码 **127 / 127**(未取得任何有效读数) |
|
|
389
|
+
|
|
390
|
+
**关于两处差异的如实标注**:① 批内的 2 个失败在本次**两次**全量(UTF-8 env 与 clean env)中**均 PASS**,
|
|
391
|
+
且在 clean env 下单独重跑 3+3 次也全 rc=0——本报告作者仍**不为其成因背书**:要判定它们是 flaky
|
|
392
|
+
还是并发写者所致,需要更多轮次与并发复现实验(见 §7-Q5);本次读数亦只是**该时刻快照**。② 容器:本机 Docker CLI 在位(`docker --version` → 28.4.0、
|
|
393
|
+
`docker compose version` → v2.39.4),但 **daemon 未运行**——`docker ps` rc=1,
|
|
394
|
+
`error during connect: … open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified`;
|
|
395
|
+
`docker images rust:bookworm` rc=1。故 `scripts/linux_verify.sh` 的两栈(其容器形态见该脚本头注
|
|
396
|
+
`docker run --rm -v <repo>:/work -w /work rust:bookworm bash scripts/linux_verify.sh [full|core]`)
|
|
397
|
+
**本轮无法复跑,记为未验证**;本报告不为 `127` 的具体成因背书(127 是 shell 的「命令未找到」类退出码,
|
|
398
|
+
但 daemon 未运行时 docker CLI 自身返回 1,两者不等价)。
|
|
399
|
+
|
|
400
|
+
### 4.5 本报告作者本次实跑命令清单(可复现)
|
|
401
|
+
|
|
402
|
+
1. `python -c` 探针:本机 locale/env + clean-env 子进程 + `-X utf8` 对照(§2 A1)
|
|
403
|
+
2. `python -c` 探针:clean env 裸 `open(p,"w").write("中文标题")` → 落盘字节 + utf-8 读回(§2 A2)
|
|
404
|
+
3. 自写 AST 扫描器(`python - <<PY` 内联,不落文件):HEAD 物化树 + 工作树全扫(§4.3)
|
|
405
|
+
4. `python scripts/verify_open_encoding.py` / `… --self-test`(§4.2)
|
|
406
|
+
5. `python scripts/test_verify_open_encoding.py`(§4.2)
|
|
407
|
+
6. `python scripts/test_utf8_boot_guard.py --no-mutations` / `python scripts/test_utf8_boot_guard.py`(§4.1)
|
|
408
|
+
7. `docker --version` / `docker compose version` / `docker ps` / `docker images rust:bookworm`(§4.4)
|
|
409
|
+
8. `git status --porcelain` / `git rev-parse HEAD` / `git archive --format=tar HEAD`(只读)
|
|
410
|
+
|
|
411
|
+
**第 2 版(读者反馈处置轮)追加的实跑**:
|
|
412
|
+
|
|
413
|
+
9. `python scripts/run_tests.py`,env 清空 `PYTHONUTF8`/`PYTHONIOENCODING`/`PYTHONLEGACYWINDOWSSTDIO`(§4.4)
|
|
414
|
+
10. 同 clean env 下 `python scripts/verify_open_encoding.py` / `… --self-test` / `python scripts/test_verify_open_encoding.py`(§3.2 末、§6.3)
|
|
415
|
+
11. 令牌闸前置对照:`python -m md_cg.mcp_server` —— 带伪造失效 `MDCG_TOKEN`(rc=3)与清空 `MDCG_*`(rc=0)(§2.1 A13′、§5.1)
|
|
416
|
+
12. 直跑文件形态对照:`python -X utf8 hive/hive_mcp/mcp_server.py`(rc=0、握手 `hive-mcp`)与 `python -X utf8 md_cg/mcp_server.py`(rc=1、`ImportError: attempted relative import`)(§4.1 注、§7-Q3)
|
|
417
|
+
13. `python -m md_cg.test_n212_n213_n224_generation_gates` / `python -m md_cg.test_p28_refcheck` 各 3 次(clean env)(§4.4)
|
|
418
|
+
14. `python scripts/workspace_index.py --check`(rc=1、陈化 2 处,均点名 `utf8_boot.py`)(§6.4 第 10 条、§7-Q9)
|
|
419
|
+
15. `git diff --numstat`(`WORKSPACE_INDEX.md` 7/6、`scripts/workspace_index.py` 2/0)(附清单)
|
|
420
|
+
|
|
421
|
+
`L1 直跑留痕:以上均为只读判定(产物落点为临时目录与 stdout,不改仓库/外部状态;唯一写动作=本报告文件本身)— 风险低 / 本任务内多次 / 可逆`。
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 5. 两项独立复核判定与 uncovered
|
|
426
|
+
|
|
427
|
+
> 「独立复核」在本报告**仅指**下列两项第三方复核(复核者各自自写扫描器/探针、只读只跑、
|
|
428
|
+
> 实验面自建在系统临时目录、不依赖执行者日志)。判定原文=**ACCEPT / ACCEPT**。
|
|
429
|
+
|
|
430
|
+
### 5.1 A · 入口自保证 UTF-8 —— **ACCEPT**
|
|
431
|
+
|
|
432
|
+
**basis(复核者自述要点)**:清空两 env 的裸解释器上独立复现 `locale.getlocale()=('Chinese (Simplified)_China','936')`、
|
|
433
|
+
`getpreferredencoding(False)=cp936`、`utf8_mode=0`、`stdout.encoding=gbk`;异常路 `UnicodeDecodeError 'gbk' codec can't decode byte 0xad in position 2`、
|
|
434
|
+
静默路 `b'\xd6\xd0\xce\xc4\xb1\xea\xcc\xe2'`。**自建 E2E 组帧**(非守卫代码):两 MCP 入口 rc=0、
|
|
435
|
+
解释器启动计数=2、stdout 严格 UTF-8 逐行 JSON、`serverInfo` 正确。**修前对照(临时物化 1 158 文件)**:
|
|
436
|
+
变体 A(只删两入口的 `ensure_utf8` 调用、保留 `_force_utf8_stdio`)→ E2E 仍绿;变体 B(再删
|
|
437
|
+
`_force_utf8_stdio()`)→ rc=0 但 stdout 非法 UTF-8(byte 0xc1 @241/249)⇒ 判定两层关系为**并存**、
|
|
438
|
+
契约(c) 的真后果是「静默产出非 UTF-8 字节」。自建 sitecustomize 启动计数含熔断:E2E 计数恰 2、
|
|
439
|
+
`-X utf8` 时 1、fail-fast 时 1。fail-fast 指引**逐个真跑**(两个 `-m` 握手成功、两个 usage、
|
|
440
|
+
`--show-config` 出 `"keys"`、两个 `--list` rc=0),反例 `python -X utf8 md_cg/mcp_server.py` → rc=1 ImportError。
|
|
441
|
+
F3 三流差分用**非 fd 0/1/2 拓扑探针**:删 `stdin=`/删 `stdout=`/`stderr=None` 三项**各自转红**。
|
|
442
|
+
F6:单进程 import 七入口启动计数=1、七个标记齐备;被 import 路径 `utf8_mode=0` 而 env 已置,
|
|
443
|
+
其孙进程 `utf8_mode=1 enc=utf-8` ⇒ 「无论走哪条路」在被 import 路上同样成立;对照复刻「仅 reconfigure」
|
|
444
|
+
者的孙进程 `utf8_mode=0 enc=gbk env=None/None`。依赖方向 AST 审计:助手 import 面 5 个纯 stdlib、无
|
|
445
|
+
`os.exec*`;import 后 `MODS=[]`。七入口锚点 AST 审计:调用锚之前只有 `__future__`/docstring/stdlib import
|
|
446
|
+
+ `_UTF8_ROOT` + `sys.path.insert`,无任何文件/库 I/O。**守卫与自证复核者独立跑**:24/24、rc=0、
|
|
447
|
+
M1..M13 红项集与退出码逐项与其复算一致;另自建 driver 在副本上把 `check_streams`/`check_e2e_mdcg`
|
|
448
|
+
架空成恒 `True` → `run_mutations` 报 miss、rc=2 ⇒ 自证机制有判别力。边界:临时
|
|
449
|
+
`MDCG_ROOT`/`STATE`/`DATA`/`AUX`/`TENANT_REGISTRY` 与 `HIVE_JOBS_DIR`,`HIVE_EXE` 指向不存在路径
|
|
450
|
+
(绝不拉真 serve);未重建/重启在役 serve;未 `git add/commit/push`;复核前后 `git status --porcelain`
|
|
451
|
+
均为 20 条。
|
|
452
|
+
|
|
453
|
+
**前置对齐(第 2 版补,【本次】;回应「同一个 md_cg 入口有三个互斥读数」)**
|
|
454
|
+
|
|
455
|
+
| 读数 | 前置(关键差异在**凭据 env** 与**身份面**) | 结果 |
|
|
456
|
+
|---|---|---|
|
|
457
|
+
| A13(批内那次) | 只清 `PYTHONUTF8`/`PYTHONIOENCODING`,**未清 `MDCG_*`**——会话 env 里带着一个失效 `MDCG_TOKEN` | rc=3、stdout 0 B(令牌闸 fail-closed) |
|
|
458
|
+
| 守卫 `E2E-MDCG` | `_env_clean() :197` 按 `_DIRTY_KEYS :162-174` **清空整个 `MDCG_*`/`PYTHON*`/`HIVE_*` 面(含 `MDCG_TOKEN`)**,再 `_mdcg_extra() :354-369` 置临时 `MDCG_ROOT`/`MDCG_AUX_ROOT`/`STATE`/`DATA` + `MDCG_TENANT_REGISTRY` 指向不存在文件 + **身份豁免面**(`MDCG_LEGACY_ENV_AUTH=1`/`MDCG_CAN_ADMIN=1`/`MDCG_LEGACY_ENV_ADMIN=1`/`MDCG_ACTOR=utf8-guard`/`MDCG_SUSTAIN=0`);另 `:210-211` 在宿主本身已开 UTF-8 模式时**追加 `PYTHONUTF8=0`** 以构造「未开」态 | rc=0、帧数 2、`serverInfo.name=mdcg-mcp`、中文 `write` 回 `ok` |
|
|
459
|
+
| 复核者的自建 E2E | 自述口径=临时 `MDCG_ROOT`/`STATE`/`DATA`/`AUX`/`TENANT_REGISTRY` + 清 env(原文未逐字列是否清 `MDCG_TOKEN`) | rc=0、启动计数 2、逐行 JSON |
|
|
460
|
+
|
|
461
|
+
本报告作者按**守卫口径**复跑该前置:`initialize` + `tools/call cg{op:write,content:"中文探针"}` →
|
|
462
|
+
**rc=0、stdout 900 B(2 帧)、`serverInfo.name=mdcg-mcp`、`write` 回 `ok`**;把同一命令改回
|
|
463
|
+
**只清两 env、留着失效令牌**(伪造字面量)→ **rc=3**(§2.1 A13′)。⇒ 三个读数**不是同一前置下的矛盾,
|
|
464
|
+
而是三种前置**:批内那次被令牌闸挡下,守卫与复核在「清凭据 env + 身份豁免面」下跑通。
|
|
465
|
+
(另:本节 basis 里的「临时物化 1 158 文件」的口径见 §4.1 末——那是 A 守卫变异自证的物化面**文件总数**,
|
|
466
|
+
含全部文件类型,与 B 的 486/489 不是同一口径。)
|
|
467
|
+
|
|
468
|
+
**uncovered(原文要点,逐条保留)**
|
|
469
|
+
|
|
470
|
+
1. **Linux / C-locale、容器与 `scripts/linux_verify.sh` 未跑**——全部结论只来自本机 Windows/CP936 现场。
|
|
471
|
+
2. **真宿主端到端未测**:未连 DSH/Claude/code CLI 的真 `mcp.json`、未起真 serve;「换台没设 env 的机器上真宿主是否通」只有进程级 `-m` 证据。
|
|
472
|
+
3. **E2E 判据对「助手」本身无判别力(实测)**:副本上只删两个 MCP 入口的 `ensure_utf8` 调用(保留 issue #39 防线)→ E2E 仍绿(计数 1);删掉两条防线才转红(stdout 0xc1)。故契约⑦「每处判据都要有定点变异打红」**在 check 粒度仍有一处未满足**:守卫变异表中 **E2E-HIVE 无任何变异能把它打红**(M1 只红 E2E-MDCG);其可打红性由复核者自查的变体 B 证明,但**该变异不在守卫定点表内**。
|
|
473
|
+
- 另两项**需设计者裁决的非阻断观察**:(a) `_set_child_env` 覆盖式写 `PYTHONIOENCODING`,使 `md_cg/test_issue39_utf8_stdio.py` 的 `gbk:strict` 模拟面前提在重启后变化(未跑该守卫复验,仅记录);(b) 契约②「`utf8_mode` 为真即立即返回(不做事)」与④「无论哪条路都置 env」字面冲突,实现取④优先(先置 env 再早退)——措辞张力,非缺陷。
|
|
474
|
+
- **(3b) 设计稿的处置(第 2 版更新)**:本路径下的设计稿**已不存在**——工程报告(本件)首版即覆盖了同路径文件(见文首说明),而该设计稿当时是 `??` 未追踪件、**无 git 历史**(`git log -- docs/eval/编码面前置_入口自保证UTF8与文本open守卫_v1.0.md` 无输出)⇒ 其正文**无留存副本**,只能从会话记录/批内采集文本重建。读者不会再读到「F2/F3/F6 三条未修」的旧表述;是否为设计稿另存副本或加墓碑,见 §7-Q8。
|
|
475
|
+
4. **`hive/exec_cmd.py`(serve 以 `HIVE_EXEC_PY` 拉起的多态转发层)未接线**——读码确认其全部 `open` 显式 `encoding='utf-8'` 且 `:258` 给派生命令 `setdefault PYTHONUTF8='1'`,但其自身 stdout/stderr 仍随 serve 继承的 locale(未实测,未判定为必须加)。
|
|
476
|
+
5. **「其余约 60 个带 `__main__` 的一次性工具」未逐一评估**(抽样确认 env 由调用方继承)。
|
|
477
|
+
6. **未实测「`md_cg.mcp_server` 被当库 import 后由第三方调 `main()`」形态**(该形态下助手按 F6 不重启,仅靠 `_force_utf8_stdio` 兜底)。
|
|
478
|
+
|
|
479
|
+
### 5.2 B · 文本模式 `open` 的守卫与存量订正 —— **ACCEPT**
|
|
480
|
+
|
|
481
|
+
**basis(复核者自述要点)**:自写扫描器(`%TEMP%\open_review\scan_open.py`):`files=489 / 文本模式内建 open=844(mode 缺失 407、非字面量 0)/ 二进制=92 / Attribute .open=66 / read_text-write_text=6`,
|
|
482
|
+
**文本模式缺 encoding 违例=0**;489 与文件系统实际 `.py` 数相等(无欠扫)。守卫 `verify_open_encoding.py` rc=0;
|
|
483
|
+
`--self-test` 判据锚 22/22;`test_verify_open_encoding.py` ALL PASS(含测试⑥ `WHITELIST=={}`)。
|
|
484
|
+
**逐点核 11 处**:`md_cg/test_n199_tokens_concurrent_write.py:157`、`md_cg/test_security_audit_v21.py:227/231`、
|
|
485
|
+
`hive/test_n144_exec_cmd_terminal_race.py:71` 与 `:117`(契约写 `:116`,**行号漂移**)、
|
|
486
|
+
`md_cg/test_lock.py:22/38`、`hive/test_stop_tree_kill.py:127`、`hive/hive_mcp/mcp_server.py:673`
|
|
487
|
+
(契约写 `:653`,漂移)**均已带 `encoding='utf-8'`**。**自建变异台**(temp 物化 489 文件+守卫副本):
|
|
488
|
+
注入裸文本 open → rc=1 且全域恰好 1 项;四文件各注一处 → 恰好 4 项;`open(p,'w',encoding='utf-8')` /
|
|
489
|
+
`open(p,'rb')` / `os.open` / `tarfile.open` / `io.open` 五类 → **全部 rc=0(不误报)**。删断言探针:
|
|
490
|
+
削掉 `md_cg/fsutil.py` 的 `os.open` → 自证 rc=2 并报「对照点位漂移」;抽掉 `md_cg/` 扫描面 → rc=2
|
|
491
|
+
ANCHOR-MISS(首次「抽空 `scripts/`」探针是**假通过**,rc=2 源于守卫文件本身不存在,已重做)。
|
|
492
|
+
`git diff`:全 diff 中 open 相关增/删各 9 行,**全部仅加 `encoding='utf-8'`**;无一条二进制 open 被改;
|
|
493
|
+
断言/期望类增删行=0;`scripts/_mdcg_reindex_dshlogs.py` 与 `md_cg/fsutil.py` diff 为空。
|
|
494
|
+
官方 runner 下 `md_cg -k security_audit_v21` / `-k test_lock` / `hive -k stop_tree` / `-k n144` /
|
|
495
|
+
`scripts -k verify_open_encoding` 全部 PASS。**清单订正**:契约第 7/8 点
|
|
496
|
+
(`scripts/_mdcg_reindex_dshlogs.py:365/:411`)**不是缺口**(`:148` 自建 `write_text`,`:150` 早已显式
|
|
497
|
+
`encoding` + `newline`,加 `encoding=` 会 `TypeError`)⇒ 实为 **9 处订正 + 2 处契约误判**;
|
|
498
|
+
契约「89 处二进制」实测 92,差额=新增未追踪件 `scripts/test_utf8_boot_guard.py` 的 3 处(扣掉即 89);
|
|
499
|
+
契约「103」= 当前 92 处「内建 open 无 encoding 关键字」+ 已订正 11 处,口径自洽。契约点名的
|
|
500
|
+
**5 个 Attribute 假阳性点位**逐个 AST 定位仍在位且不报。全程未编辑工作区任何文件,
|
|
501
|
+
复核前后 `git status --porcelain` 逐字一致。
|
|
502
|
+
|
|
503
|
+
**uncovered(原文要点,逐条保留)**
|
|
504
|
+
|
|
505
|
+
1. **全量 python 套件未跑**:ask 末行明示「不要重复跑全量与容器」,与该 ask 的「验证⑤ python 全量套件不因订正转红」存在张力;复核者只用官方 runner 跑了 11 处改动直接触及的 5 个模块 + 守卫测试(结果全 PASS),**未跑全量 ⇒ 该条按「未执行」计,不按通过计**。(**本报告作者已补跑**:269/269、rc=0,见 §4.4。)
|
|
506
|
+
2. **契约的「11 处逐点订正」**:复核者只确认 9 处属真订正,另 2 处(`scripts/_mdcg_reindex_dshlogs.py:365/:411`)经独立取证判定为契约误分类、**本就不该改**;若验收方按字面要求「11 处全部出现 `encoding=`」,则该字面条件**不可满足**,且复核者不认为应满足。
|
|
507
|
+
3. **守卫是否已接入 CI 门禁**(`.github/workflows` 或 `scripts/run_tests.py` 之外的闸口)未核查——契约未提出该要求。
|
|
508
|
+
4. **契约所称「真实仓 89 处二进制对照」**:复核者按当前实测 92 处做对照,并给出 89 的口径复现(扣除新增未追踪件的 3 处),**未在 HEAD 快照上单独复现 89**。(**本报告作者已补**:HEAD 物化树实测 89,见 §4.3。)
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## 6. 边界与未覆盖
|
|
513
|
+
|
|
514
|
+
### 6.1 口径更正(本批最需要写清的一件事)
|
|
515
|
+
|
|
516
|
+
| 轮次 | 口径 | 复现情况 | 判定 |
|
|
517
|
+
|---|---|---|---|
|
|
518
|
+
| 早期(编排会话先说) | **224 处裸 `open`** | **出处不闭环,且不可复现**(第 2 版写明)。224 的**原始命令与口径**在批内采集文本、本报告可取证范围内**均无留存**——本报告与批内记录都取不到它的来源 ⇒ 只能判为**来源不明的历史口径,无依据可核**。为逼近它试过的三个**文本/正则代理口径**也都不吻合,且三者互不相等:`open(` 文本出现次数 = 1 015(`md_cg` 707 / `hive` 211 / `scripts` 97);「含 `open(` 且同行无 `encoding` 字样」= 198(其中同行无二进制字面量者 108);正则数二进制字面量 open = 58。三者与 224 的关系=**都不等于 224**,也不是 224 的分解 | **不可作判据**;本报告**未复跑**这三种代理口径,且任何 AST 口径(913 / 936 / 844 / 824)都得不出 224 |
|
|
519
|
+
| 中期(编排会话改口) | **103 处「裸 open」** | **可精确复现**(放宽为「`Name` open | `Attribute` `.open`」且不做二进制豁免、也不区分内建/Attribute 时,三目录「无 encoding」恰为 103) | 数字本身自洽,但**混入两类非缺口**,故不可作缺口判据 |
|
|
520
|
+
| 最终(本批结论) | **89 处内建二进制 + 9 处文本模式真缺口**(+ 5 处 Attribute 假阳性 = 103) | **两侧独立复现**:批内扫描器与【本次】本报告作者自写扫描器、HEAD 物化树与工作树四条路径一致(§4.3) | **唯一能同时对上源码的口径** |
|
|
521
|
+
|
|
522
|
+
**103 的精确分解(HEAD 时点)**:`89` 内建二进制(**加了 encoding 是非法**,见 B6)+
|
|
523
|
+
`9` 内建文本缺口(=§1.2 的 9 行,现树已 0)+ `5` 处 Attribute 形态无 `encoding`
|
|
524
|
+
(`md_cg/fsutil.py:204 os.open`、`md_cg/lexicon/build_cedict_en_zh.py:60 io.open "wb"`、`:62 gzip.open "rb"`、
|
|
525
|
+
`scripts/check_publish_artifact.py:696 tarfile.open r:gz`、`scripts/criteria_fingerprint.py:45 tarfile.open`)
|
|
526
|
+
= **103**。即「103 里 89 处是二进制」这句话**只对内建子集成立**;按 open-类调用整体算是
|
|
527
|
+
**91 处二进制形态**(89 内建 + 2 Attribute 二进制)。
|
|
528
|
+
|
|
529
|
+
**103 的两套分解(第 2 版补,回应「同一个 103 给出两套自称精确的分解」)**:§5.2 复核 basis 里引的是复核者的
|
|
530
|
+
「`103` = 当前 **92** 处『内建 open 无 encoding 关键字』+ 已订正 **11** 处,口径自洽」;本节给的是
|
|
531
|
+
「HEAD 时点 **89 + 9 + 5**」。**两式数值相等(都是 103),但成分不同、不是同一次扫描的分解,不能互相印证**:
|
|
532
|
+
`92 = 89 + 3`(那 3 处是**新增未追踪件** `scripts/test_utf8_boot_guard.py` 的二进制 open),
|
|
533
|
+
`11 = 9 + 2`(那 2 处正是本节判为误分类的 `write_text` 调用)——等式只靠 `5 = 3 + 2` 这个**数值巧合**成立。
|
|
534
|
+
以**能逐点对到源码行**的「89 + 9 + 5(全为 HEAD 时点)」为准;复核者那式是其**口径自述**(本轮未按点复核其 11 的真伪)。
|
|
535
|
+
|
|
536
|
+
**一处必须点名的偏差**:任务书把最终真值表述为「**89 处二进制 + 11 处文本模式真缺口**」。
|
|
537
|
+
本报告作者据**两项独立复核 + 自身读码与扫描**认定:**11 是订正前的契约清单点数,其中 2 处
|
|
538
|
+
(`scripts/_mdcg_reindex_dshlogs.py:365/:411`)经实读为模块内自建 `write_text` 的**普通函数调用**
|
|
539
|
+
(该文件 `:148-151` 已显式 `encoding="utf-8", newline="\n"`),机械加参会 `TypeError` 打断脚本**
|
|
540
|
+
⇒ **真缺口为 9**,`89 + 9 = 98`。**排除「11」的依据是这两处点位的第一手实读**(helper 源码 + `TypeError` 实测),
|
|
541
|
+
**不是算术**——第 1 版曾用「`89 + 11 = 100` 不等于任何一轮总数」作旁证,该论证**不成立**
|
|
542
|
+
(各轮总数 913 / 936 / 844 / 824 与 100 本来就不会相等,故排除不了 11),本版**删除该句**;
|
|
543
|
+
同理 `89 + 9 + 5 = 103` 只是与放宽口径的**同源复算**,也不构成独立印证。
|
|
544
|
+
(两类原始读数见 §2.2 B3/B4、§4.3。)
|
|
545
|
+
|
|
546
|
+
### 6.2 守卫的假阳性教训(判据只认内建 `open`)
|
|
547
|
+
|
|
548
|
+
- **外形匹配必误报**:按 `attr == 'open'` 匹配会把 `os.open` / `io.open` / `tarfile.open` / `gzip.open` /
|
|
549
|
+
`zipfile.ZipFile.open` 一并算作裸 `open`;三目录内 Attribute `.open` 共 **66** 处,其中无 `encoding` 的
|
|
550
|
+
**5** 处全是这类(§2.2 B7),**加 `encoding=` 到这些点位上是错的**:
|
|
551
|
+
`os.open` 第二实参是 int 标志位、`os.open` **没有 encoding 参数**;`tarfile.open(mode="r:gz")` 的 mode
|
|
552
|
+
不含 `'b'` 时会落进「文本模式」分支而被误判(`check_publish_artifact.py:696`、`criteria_fingerprint.py:45`);
|
|
553
|
+
`io.open("wb")`/`gzip.open("rb")` 本就是二进制流。⇒ 判据必须落在**语法意义**(`ast.Name` 且
|
|
554
|
+
`id=='open'`,再看 mode/encoding 关键字),不落在外形上。
|
|
555
|
+
- **二进制模式必须显式豁免**:`open(p,'wb'|'ab'|'rb', encoding='utf-8')` 三种模式全部抛
|
|
556
|
+
`ValueError: binary mode doesn't take an encoding argument` ⇒ 89/92 处二进制不在「缺 encoding」的统计面内。
|
|
557
|
+
- **同名函数的第二类假阳性**:`write_text(p, text)` 形态的**模块内自建同名函数**不是 `Path.write_text`;
|
|
558
|
+
全仓裸名 `write_text` 调用恰为 `scripts/_mdcg_reindex_dshlogs.py:365/:411` 两处 ⇒ 把它们当成缺口是误分类。
|
|
559
|
+
- **两类教训已固化为锚**(守卫因此「陈化即报」):`_ANCHOR_CORPUS` 的 22 片段含 `attr_os_open`/`attr_io_open`/
|
|
560
|
+
`attr_tarfile_open`/`attr_gzip_open`/`attr_zipfile_open` 预期 0;`_ATTR_CONTROL_FILES`(5 文件)+
|
|
561
|
+
`_ATTR_CONTROL_POINTS`(5 点位级)把「被误报的那一类形态仍在对照面上」钉死,漂移即 rc=2。
|
|
562
|
+
- **口径工具本身的教训**:正则口径数二进制字面量只得 **58**(AST 得 89/92),说明文本/正则口径会**漏数**;
|
|
563
|
+
守卫与复核都改用 AST。
|
|
564
|
+
|
|
565
|
+
### 6.3 覆盖边界(守卫**不**覆盖什么)
|
|
566
|
+
|
|
567
|
+
| 面 | 覆盖 | 依据 |
|
|
568
|
+
|---|---|---|
|
|
569
|
+
| 三目录 `.py` 的**内建** `open` 文本模式调用 | ✅ | `scripts/verify_open_encoding.py:59` `SCAN_DIRS`;`:144` 只认 `ast.Name`+`id=='open'` |
|
|
570
|
+
| 三目录 `Path.read_text`/`write_text` 缺 `encoding` | ✅ | 判据④(本次实测 `read_text/write_text` 面 **6 处、缺 encoding 0**) |
|
|
571
|
+
| 三目录**二进制**模式内建 `open` | ✅(判为**不必**加) | 判据② |
|
|
572
|
+
| 七个进程入口的进程级 UTF-8 自保证 | ✅ | `utf8_boot.ensure_utf8` + 守卫 `ANCHOR`/`GUIDE`/`IMPORT`/`STREAMS` |
|
|
573
|
+
| **非 Python 文本面**(`.ts`/`.rs`/`.md`/`.json`/`.sh` 的读写) | ❌ 不覆盖 | 守卫只 walk `*.py`;TS/Rust 侧仍靠既有 `PYTHONUTF8` 注入面(`src/lib/mdcg_client.ts:117-118`)与「全 UTF-8」约定 |
|
|
574
|
+
| Python **间接文本 I/O**:`os.fdopen` / `tempfile.*` / `codecs.open` / `subprocess(..., text=True)` / 经别名或变量调用的 `open` | ❌ 不覆盖(多数**不计入**) | 判据只认 `ast.Name`+`id=='open'`;非本形态既不判违例也不计数 |
|
|
575
|
+
| **A 路守卫自身**的控制台自保证 | ✅(`scripts/test_utf8_boot_guard.py` 顶部调 `ensure_utf8`) | — |
|
|
576
|
+
| **B 路守卫自身**的控制台自保证 | ❌ **无**(已知未接项;第 2 版补实测后果) | `scripts/verify_open_encoding.py` 未接 `ensure_utf8`(§3.2 末)。clean env 下实测:正跑 **rc=1**、stdout 72 B **GBK 字节**、崩于 `:476` 的 `print("✔ …")`;`--self-test` 同因崩于 `:444`;`scripts/test_verify_open_encoding.py` rc=0 但 stdout 亦非 UTF-8 |
|
|
577
|
+
| 除此之外的仓根其余 `.py`(三目录之外) | 不计(**额外查证**:本轮扩扫全仓,三目录之外文本裸 `open` = **0**) | §2.2 B8 |
|
|
578
|
+
|
|
579
|
+
### 6.4 未覆盖 / 未验证清单(据本批两项复核的 uncovered + 本次核查)
|
|
580
|
+
|
|
581
|
+
1. **Linux / C-locale / 容器**:全部结论只来自本机 Windows/CP936 现场;**容器两栈退出码 127/127,本次未复跑**
|
|
582
|
+
(本机 docker daemon 未运行,§4.4)⇒ 容器面**未验证**。
|
|
583
|
+
2. **真宿主端到端未测**:未连 DSH/Claude/code CLI 的真 `mcp.json`、未起真 serve。
|
|
584
|
+
3. **check 粒度的一处定点变异缺口**:`E2E-HIVE` 在守卫变异表中**没有任何变异能把它打红**(M1 只红 `E2E-MDCG`);
|
|
585
|
+
其可打红性由复核者的场外变体 B 证明,但**该变异不在守卫定点表内** ⇒ 契约⑦「每处判据都要有定点变异打红」
|
|
586
|
+
在此粒度未完全满足。
|
|
587
|
+
4. **`hive/exec_cmd.py` 未接线**(其自身 stdout/stderr 仍随 serve 继承的 locale;未实测,未判定为必须加)。
|
|
588
|
+
5. **「其余约 60 个带 `__main__` 的一次性工具」未逐一评估**。
|
|
589
|
+
6. **未实测**「`md_cg.mcp_server` 被当库 import 后由第三方调 `main()`」形态(该形态按 F6 不重启,仅靠
|
|
590
|
+
`_force_utf8_stdio` 兜底)。
|
|
591
|
+
7. **B 路的「11 处逐点订正」字面不可满足**(9 真 + 2 误分类),见 §6.1;守卫是否接入 CI 门禁未核查。
|
|
592
|
+
8. **两条非阻断的设计张力**(交设计者裁决):(a) `_set_child_env` 覆盖式写 `PYTHONIOENCODING`,
|
|
593
|
+
使 `md_cg/test_issue39_utf8_stdio.py` 的 `gbk:strict` 模拟面前提在重启后变化(未跑该守卫复验);
|
|
594
|
+
(b) 契约②与④的字面冲突(实现取「先置 env 再早退」)。
|
|
595
|
+
9. **本报告自身的边界**:作者只读跑码 + 只写本文件一件;未改任何源码;未 `git add/commit/push`;
|
|
596
|
+
未重启在役 hive serve;未触碰在役数据根(探针与 HEAD 物化面全在 `%TEMP%`)。所有读数为**对应时刻快照**
|
|
597
|
+
(批内已记录会话期间有并发写者改动七入口,计数在会话内漂移:文本 840→844、二进制 89→92)。
|
|
598
|
+
10. **B 路守卫自身在未设 `PYTHONUTF8` 的机器上不可用**(第 2 版实测新增):`verify_open_encoding.py`
|
|
599
|
+
会以 `rc=1`(与守卫自己「存在违例」的退出码**同码**)崩在「✔」的打印上(`UnicodeEncodeError`),
|
|
600
|
+
其 stdout 落 GBK 字节(§3.2 末、§6.3)⇒ 该守卫的绿/红结论**只在已开 UTF-8 模式的现场成立**;
|
|
601
|
+
换机器需调用方注入 env、或给守卫补接 `ensure_utf8`(建议与裁决归属见 §7-Q7)。
|
|
602
|
+
11. **工作区索引当前陈化 2 处**(第 2 版实测新增):`python scripts/workspace_index.py --check` → **rc=1**,
|
|
603
|
+
两条 DRIFT 均点名 `utf8_boot.py`「登记但不存在」——根因是索引从 **git 追踪面**(`git ls-files`)生成,
|
|
604
|
+
而 `utf8_boot.py` 尚未追踪(`??`)。`scripts/workspace_index.py` 已把该件登记进 `ROOT_FILES`
|
|
605
|
+
(`git diff` 2 行),`WORKSPACE_INDEX.md` 已随之重生成(HEAD 快照 `d7c6e347`→`174cd25f`、追踪件 1998→2076)。
|
|
606
|
+
**随批 `git add` 即自动消失**(详见附清单与 §7-Q9)。
|
|
607
|
+
12. **224 的出处不闭环**:无命令/口径留存、不可复现(§6.1),本报告无法回溯它曾否有过依据。
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## 7. 读者待决问题:本报告的判定、建议与新增证据
|
|
612
|
+
|
|
613
|
+
> 本节逐条回应读者反馈里的 `nextQuestions`(10 条)。**属裁决权的事项一律只给建议、标明归属,
|
|
614
|
+
> 本报告不代为决定**;本轮能补的证据已补(标注【本次】,命令见 §4.5 第 9–15 条)。
|
|
615
|
+
|
|
616
|
+
| Q | 问题 | 本报告能给的判定 / 新证据 | 归属与建议 |
|
|
617
|
+
|---|---|---|---|
|
|
618
|
+
| Q1 | 9 与 11 的偏差由谁裁决、契约文本是否回改 | 依据=两处点位的**第一手实读**(helper `:148-151` 已显式 + 机械加参 `TypeError`,§6.1);`11` 是订正前清单点数,`9` 是真缺口(§4.3 两侧扫描一致)。**契约原文不在本报告读取面内,本报告无法回改它** | **验收方裁决**:建议按「9 真缺口 + 2 处误分类」记通过,并在提交说明与契约文本中把 11→9 回改(§7-Q9) |
|
|
619
|
+
| Q2 | 容器两栈与 Linux/C-locale 面何时补跑、走本机 Docker 还是 CI | 本轮取证:本机 docker CLI 在位(28.4.0)但 **daemon 未运行**(`docker ps` rc=1,`//./pipe/dockerDesktopLinuxEngine` 不存在),故本机复跑**当前不可行**;容器形态在该脚本头注:`docker run --rm -v <repo>:/work -w /work rust:bookworm bash scripts/linux_verify.sh [full\|core]` | **裁决/排期归使用者**:建议启动 Docker Desktop 后跑 `core`,或落 CI;在跑通前,容器面与 Linux/C-locale 面在本批**记为未验证** |
|
|
620
|
+
| Q3 | 要不要给 `E2E-HIVE` 补一个能打红它的定点变异 | 新证据(【本次】):**单点变异不可能打红它**——M1 只让 md_cg 入口死(`md_cg/mcp_server.py:3850` 相对 import ImportError),hive 入口直跑仍 rc=0 握手成功(§4.1 末)。能打红它的只有**双点**变异(同时停用 `ensure_utf8` 与 `_force_utf8_stdio`),而变异表是单点注入(`_sub :905-907`) | **裁决归使用者**:建议下一批给 `_MUTATIONS` 增一条**双点注入**的 M14(或把 E2E 判据拆出「仅 `_force_utf8_stdio` 生效」的子断言);在此之前,契约⑦在该 check 粒度**按字面未满足**,本报告按「未满足」记而不按通过记 |
|
|
621
|
+
| Q4 | 全量套件是在 `PYTHONUTF8=1` 下跑的,clean env 下再跑是否同样绿 | **已补跑**(【本次】):clean env(清 `PYTHONUTF8`/`PYTHONIOENCODING`/`PYTHONLEGACYWINDOWSSTDIO`、不带 `-X utf8`)下 `python scripts/run_tests.py` → **269/269 通过、5 跳过、rc=0、100.7s**,且 stdout 合法 UTF-8(runner 自身 `ensure_utf8` 重启了自己)。**套件层面验证到的层**=「入口自保证 + 后代 env 继承」两层:runner 自己重启 → 给子进程注入 env → 各入口再自保证 | 已闭环,无需裁决;若要更强的「逐入口在未开模式下自证」,那是 §4.1 A 守卫的 `ONCE/FAILFAST/NOOP/GUIDE` 面(已在守卫内跑) |
|
|
622
|
+
| Q5 | 批内失败的两个模块要定成 flaky 还是并发写者 | **已补**(【本次】):clean env 下各跑 3 次 → **6/6 全 rc=0**(n212 每次 `PASS=42 FAIL=0`);两次全量(UTF-8 env 与 clean env)里这两个模块也都 **PASS** | **已闭环(本机此刻不复现)**;本报告**不判定**其历史成因(要区分 flaky 与并发写者需更多轮次/并发复现实验),也不为其背书 |
|
|
623
|
+
| Q6 | `hive/exec_cmd.py` 与「其余约 60 个带 `__main__` 的一次性工具」是否纳入下一批、判据是什么 | 现状读码(复核口径):`exec_cmd.py` 全部 `open` 显式 `encoding='utf-8'`,且 `:258` 给派生命令 `setdefault PYTHONUTF8='1'`;其**自身** stdout/stderr 仍随 serve 继承的 locale(未实测是否有害) | **裁决归使用者**:建议判据取「**其 stdout/stderr 会被宿主或调用方按 UTF-8 解析**的进程入口」+「**会派生后代且后代默认编码随 locale** 的入口(后者用 env 传递即可)」;按此判据 `exec_cmd.py` 属「诊断输出会被读」一类,宜接 `ensure_utf8`(成本=一次 `sys.path` 插入 + 一行调用) |
|
|
624
|
+
| Q7 | B 路守卫自身未接 `ensure_utf8`,在没设 env 的机器上会不会落 GBK | **已实测(【本次】)且比读者设想的更严重**:clean env 下 `verify_open_encoding.py` **rc=1 + 崩在 `:476` 的 `✔` 打印(`UnicodeEncodeError: 'gbk' codec can't encode '\u2714'`)+ stdout 72 B 全 GBK**;`--self-test` 同因崩于 `:444`;`scripts/test_verify_open_encoding.py` rc=0 但 stdout 亦非 UTF-8。**`rc=1` 与「存在违例」同码**⇒ 未设 env 的机器上「崩溃」会被读成「有违例」 | **裁决归使用者**:建议与 A 路守卫对齐(该文件顶部加 `sys.path` 插入 + `ensure_utf8(__file__)`);若担心「守卫被 import 时重启」的副作用,助手的 F6 分支已保证被 import 时只置 env 不重启 |
|
|
625
|
+
| Q8 | 同路径的旧设计稿怎么处置(删/改名/加墓碑) | 现状(【本次】):**该路径下已无独立设计稿**——工程报告首版覆盖了它;`docs/eval/` 下无第二份「编码面」文件;`git log -- docs/eval/编码面前置_…md` 无输出,即**设计稿正文无 git 留存副本**(当时是 `??`) | **裁决归使用者**:若要保留设计稿,只能从会话记录重写并另存名(如 `…_设计_v1.0.md`)或加墓碑;现状不再有「读者先读到三条未修」的问题(§5.1 uncovered 3b 已更新) |
|
|
626
|
+
| Q9 | 14 `M` + 6 `??` 是否现在提交、提交说明是否写「11→9」 | 未提交是**硬边界**(本批与本次修订都不得 `git add/commit/push`)。**新证据(【本次】)**:`python scripts/workspace_index.py --check` → rc=1、陈化 2 处,均点名 `utf8_boot.py`「登记但不存在」,根因=索引取 **git 追踪面**而该件未追踪 ⇒ **提交后自动消失**(§6.4 第 11 条) | **裁决归使用者**:建议提交时在说明里写明「契约 11 处 → 实为 9 处真缺口 + 2 处误分类(`_mdcg_reindex_dshlogs.py:365/:411` 为模块内 helper 调用,加 `encoding=` 会 `TypeError`)」,并附 `--check` 由红转绿的验证 |
|
|
627
|
+
| Q10 | 真宿主端到端(真 `mcp.json`、真 serve)何时验、由谁验 | 现状:只有进程级 `-m` 证据(两 MCP 入口 initialize 握手成功),未连真宿主、未起真 serve(§5.1 uncovered 2) | **裁决归使用者**:建议由持有真宿主(DSH/Claude/code CLI)环境的一方在下一批验;本报告不把进程级证据冒充宿主级证据 |
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
### 附:本批新增/改动件清单(本报告作者读码所见)
|
|
632
|
+
|
|
633
|
+
| 件 | 状态 | 规模 |
|
|
634
|
+
|---|---|---|
|
|
635
|
+
| `utf8_boot.py` | `??` 新增 | 238 行 / 15 356 B / 无 BOM |
|
|
636
|
+
| `scripts/test_utf8_boot_guard.py` | `??` 新增 | 1 254 行 / 66 870 B / 无 BOM(11 判据 + 13 变异) |
|
|
637
|
+
| `scripts/verify_open_encoding.py` | `??` 新增 | 482 行 / 24 253 B / 无 BOM |
|
|
638
|
+
| `scripts/test_verify_open_encoding.py` | `??` 新增 | 215 行 / 10 647 B / 无 BOM(26 条 `assert`) |
|
|
639
|
+
| 七个入口(各插锚块,零业务语义改动) | `M` | `hive/hive_mcp/mcp_server.py`、`md_cg/mcp_server.py`、`hive/exec.py`、`hive/orch.py`、`hive/serve_start.py`、`md_cg/run_tests.py`、`scripts/run_tests.py` |
|
|
640
|
+
| 5 个测试文件共 9 处 `open` 加 `encoding` | `M` | `md_cg/test_n199_tokens_concurrent_write.py`、`md_cg/test_security_audit_v21.py`、`hive/test_n144_exec_cmd_terminal_race.py`、`hive/test_stop_tree_kill.py`、`md_cg/test_lock.py` |
|
|
641
|
+
| `scripts/workspace_index.py` | `M` | **本批的第 18 条(工作区索引)收尾件**:把 `utf8_boot.py` 登记进 `ROOT_FILES`(`git diff` 2 行,新增该件的职责文案),本报告作者读 diff 确认 |
|
|
642
|
+
| `WORKSPACE_INDEX.md` | `M` | 随上条**重生成**的产物:快照 `HEAD d7c6e347 → 174cd25f`、追踪件 1998→2076,`md_cg/` 776→813、`hive/` 134→142、`scripts/` 45→57、`test/` 50→54、`docs/` 131→148(`git diff --numstat` 7/6 行) |
|
|
643
|
+
|
|
644
|
+
**合计**:`M` **14 个**(七入口 + 五个测试文件 + 两个索引件)+ `??` **6 个**(本批新件 4 个 + `.zcode/` + 本报告文件本身)
|
|
645
|
+
——与文首「工作树」行的计数**同口径**;第 1 版附录少列了后两个索引件(第 2 版补齐)。
|
|
646
|
+
`python scripts/workspace_index.py --check` 当前 rc=1 的 2 处陈化即因 `utf8_boot.py` 尚未追踪所致(§6.4 第 11 条)。
|