@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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# N225 索引/日志非对象装载面类型闸 —— 修复工程报告 v1.0
|
|
2
|
+
|
|
3
|
+
- 日期:2026-09-29
|
|
4
|
+
- 修订:v1.0-r1(按读者反馈逐条处置:§1 表格拆「修前/修后」两列、§2 补 `append_jsonl` 行号与标志名澄清、§4.2 补容器命令与计数关系、§5.1 补 gate 依赖 `git add` 的机制、§5.3 补「指纹对齐」与 N230 指路、§5.4 补 `git diff` 四条实测、新增 §7 读者问题处置)
|
|
5
|
+
- 仓库:`D:/program/dsh-memory-main`(下称 `<repo>`)
|
|
6
|
+
- 变更面(实跑 `git status --porcelain` 所见):`M md_cg/fsutil.py`、`M md_cg/mdcg.py`、`M scripts/linux_verify.sh`、`?? md_cg/test_n225_nonobject_load.py`(另有 `M package-lock.json`,与本次修复无关,见 §5.4)
|
|
7
|
+
- 未 `git add` / 未 `commit` / 未 `push`
|
|
8
|
+
- 本报告只写一行落盘产物(本文件);未改任何源码或其它文件
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 0. 结论速览
|
|
13
|
+
|
|
14
|
+
| 项 | 结论 | 证据 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 缺陷 | `json.loads` 的**合法**产出可以不是对象(`null`/`[]`/`123`/`"abc"`),三处判据裸调 `.get`,而 `except` 元组只收 `(ValueError, OSError)` ⇒ `AttributeError` 逃逸,整条装载链断 | §1、§2 |
|
|
17
|
+
| 影响面 | **构造期**即失败 ⇒ 所有构造 `MdCG` 的调用方(含 stdio 主循环)连带失败;`compact` 收尾落不成快照。服务层 `rc=1` 有采集实测(§4.2「复现环节 exp3 的 OLD 对照」:rc=1、应答 0 条、traceback 落 `md_cg/mdcg.py:1028`);「一次性脚本全崩」是构造期抛异常的**直接推论**(未单独实测脚本层)。腿③ 单次临时库观察到的盘面状态是「两个 `.md` 未动、`_index.json` 仍为非对象」——**该观察只支持「这一次调用没有清池」,不构成对整个失败族的普遍断言**(未做生产库对照,也未覆盖写盘中途失败等其它路径) | §2 三腿、§4.2 |
|
|
18
|
+
| 修法 | 非对象**与既有 `ValueError`/`OSError` 同一降级口径**(视同损坏 → 走既有全库扫描/`scan_fallback`);日志面逐行分流 + 非对象行记账告警 | §3 |
|
|
19
|
+
| 本报告实跑验证 | 守卫 `56 通过 / 0 失败` rc=0;变异自证 `PASS`(红项恰好 25/16/15)rc=0;`run_tests -k n225` PASS 1/1 rc=0;`run_tests md_cg` 190/190 rc=0 | §4.1 |
|
|
20
|
+
| 未由本报告复跑的验证 | 修前三条腿取证、独立复核实验、python 全量、容器两栈、`npm run gate` | §4.2、§6 |
|
|
21
|
+
| 已知缺口(含不确定度) | 类型闸只查**深度 1**:同族失败模式(dict 行坏键型、载荷非对象、`nodes` 内含非对象)在深度 2 复发且不在断言面内——三处均为采集方标注 medium/low 的待处置项(本次修法在 §3 明示「不新增第三套判据」,未宣称覆盖深度 2)。§5.3 表中**最后一类(同族第二组 9 处)来源方自标为「读码判断,未实测」**,本报告照录、不升级为结论。另有记账面无生产消费者、不落盘 | §5.3 |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. 缺陷定义与真实站点
|
|
26
|
+
|
|
27
|
+
**缺陷定义**:N225 的攻击面是「**合法 JSON 但顶层非对象**」——`null`、`[]`、`123`、`"abc"`。`json.loads` 对它们全部成功返回,因此 `except ValueError` 类防线看不见它们;而下游判据直接把它当对象用(`d.get(...)` / `r.get(...)`),抛出的 `AttributeError` 不在捕获元组内,于是异常穿透到构造期与工具层。
|
|
28
|
+
|
|
29
|
+
**真实站点(本报告读码所见)**。下表把**修前文本**与**修后文本**分成两列——「后果」列一律描述**修前文本**的行为;修后的行为见 §3:
|
|
30
|
+
|
|
31
|
+
| # | 修前文本(`git show HEAD:...` 实读,行号为修前行号) | 修后文本(工作区实读,行号为当前行号) | 修前文本的后果 |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| ① 装载面 | `md_cg/mdcg.py:1050`:`if d.get("schema") == SCHEMA:` ← **没有 `isinstance` 守卫**(`if` 与 `except` 见 `HEAD:1047-1053`) | `md_cg/mdcg.py:1057`:`if isinstance(d, dict) and d.get("schema") == SCHEMA:` | 顶层非对象时裸 `d.get` 抛 `AttributeError`,而修前 `HEAD:1052` 的 `except (ValueError, OSError)` 不接 ⇒ `MdCG.__init__`(`md_cg/mdcg.py:1028` 调 `_load_index`)**构造即失败** |
|
|
34
|
+
| ② 分片日志面 | `md_cg/fsutil.py:386`:`recs.sort(key=lambda r: (r.get("_t", 0), r.get("_s", 0)))` ← **排序键前无任何行类型分流** | `md_cg/fsutil.py:451` 同上(排序键文本未变);分流新增在 `md_cg/fsutil.py:436-444`(`isinstance(rec, dict)` 判定) | 非对象行直接进 `recs`,排序键对非 dict 裸调 `.get`;单元素列表也调键函数 ⇒ **n=1 即崩** |
|
|
35
|
+
| ③ compact 面 | `md_cg/mdcg.py:1235`:`if d.get("schema") == SCHEMA:` ← **没有 `isinstance` 守卫**(`try` 在 `HEAD:1232`、`except` 在 `HEAD:1239`) | `md_cg/mdcg.py:1242-1249` 新增 `if not isinstance(d, dict): scan_fallback = True`;原判据降为 `elif d.get("schema") == SCHEMA:`(`md_cg/mdcg.py:1250`);`else: scan_fallback = True`(schema 不符)在 `1252-1253` | 崩点(修前 `HEAD:1235` 的 `d.get`)落在修前 `HEAD:1232` 的 `try` 内,而 `except`(修前 `HEAD:1239`)只收 `(ValueError, OSError)` ⇒ **在「顶层非对象」这一类载荷下**标志 `scan_fallback` 保持 `False`,「宁重扫勿清池」走不到(该标志的命名与适用范围见 §2 腿③ 末段) |
|
|
36
|
+
|
|
37
|
+
**锚点漂移的行数核算(实跑 `git diff -U0 -- md_cg/fsutil.py` 的 hunk 头逐块加总)**:台账旧锚点 `mdcg.py:1050` / `fsutil.py:386` / `mdcg.py:1235` 经 `git show HEAD:md_cg/mdcg.py`、`git show HEAD:md_cg/fsutil.py` 逐行核对,与修前 `HEAD` 版本一致;漂移量按 hunk 分解如下——
|
|
38
|
+
|
|
39
|
+
| `md_cg/fsutil.py` 的 hunk(`git diff -U0` hunk 头) | 净增行 | 位置相对 `HEAD:386`(排序键) |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `@@ -265,0 +266,50 @@` | +50 | **之前**(记账设施整块,工作区 `266-315`) |
|
|
42
|
+
| `@@ -352 +402 @@` | 0(1 行换 1 行) | 之后(`read_all` 的「生效条件」注释改词) |
|
|
43
|
+
| `@@ -367,0 +418,8 @@` | +8 | **之前**(`read_all` docstring 的 N225 段) |
|
|
44
|
+
| `@@ -378 +436,8 @@` | +7 | **之前**(`read_all` 内新增的分流语句;工作区行号 `436-444`) |
|
|
45
|
+
|
|
46
|
+
**+65 = 50 + 8 + 7**,与 `386 → 451` 的位移相符,且四块全部落在 `HEAD:386` 之前。注意:分流行的工作区行号 `436-444` **看上去**大于 386,但它在 `HEAD` 里的位置是 `378` 附近(前面已插入 65 行才被推到 436)——「分流语句在 386 之后,故不该造成位移」是把两套行号混用后的错觉。另:记账设施整块的插入范围是工作区 `266-315`(50 行),§5.3 行内引用的 `276-313` 只是其中的变量与函数体片段(不含块首 10 行注释头与块尾空行),按它数行数会少算。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. 修前现场取证(改动前,由本工作流「缺陷复现员」取得)
|
|
51
|
+
|
|
52
|
+
以下三条腿**逐条列 payload / 异常 / 崩溃点**。来源=本工作流缺陷复现环节采集(§4.2 标注为「未由本报告复跑」);每条腿都在系统临时目录合成库上跑,跑完 `rmtree`,只用只读手段。
|
|
53
|
+
|
|
54
|
+
### 腿 ① 快照装载
|
|
55
|
+
|
|
56
|
+
- **payload**:`null` / `[]` / `123` / `"abc"` 四种非对象形态**逐项独立实跑**——各建一个临时库根,把 `_index.json` 写成该形态后构造 `MdCG`。
|
|
57
|
+
- **observed**:四种形态**全部崩**,消息随载荷类型变:
|
|
58
|
+
- `null` → `AttributeError: 'NoneType' object has no attribute 'get'`
|
|
59
|
+
- `[]` → `'list' object has no attribute 'get'`
|
|
60
|
+
- `123` → `'int' object has no attribute 'get'`
|
|
61
|
+
- `"abc"` → `'str' object has no attribute 'get'`
|
|
62
|
+
- **崩溃链**:`MdCG.__init__`(`md_cg/mdcg.py:1028`)→ `_load_index`(修前 `HEAD:1050` 的 `d.get`)。构造期的 `except (ValueError, OSError)`(修前 `HEAD:1052`)捕不住 `AttributeError` ⇒ **失败发生在构造期,不是工具层**。
|
|
63
|
+
- **站点**:`md_cg/mdcg.py:1050`(`HEAD` 口径)。
|
|
64
|
+
|
|
65
|
+
### 腿 ② 分片日志重放
|
|
66
|
+
|
|
67
|
+
- **payload**:一行 `null` 分片记录。用的是仓库自己的写点:`fsutil.append_jsonl(<root>/_index_log/99999-nullprobe.log, None)`,落盘字节为 `b'null\r\n'`。
|
|
68
|
+
- **`b'null\r\n'` 的成因与「是否仓库既有行为」**:`append_jsonl` 定义在 `md_cg/fsutil.py:189`,其写点是 `md_cg/fsutil.py:204` 的 `fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_APPEND, 0o600)`——**未带 `os.O_BINARY`**;Windows CRT 文本模式把 `\n` 转成 `\r\n`。实跑 `git show HEAD:md_cg/fsutil.py` 核对:`HEAD:189` 与 `HEAD:204` 与工作区**逐字相同**(该文件本次 diff 无这两个函数的 hunk)⇒ 这是**仓库既有行为**,不是这条实验路径的偶发现象。`\r\n` 不影响判定:`read_jsonl`(`md_cg/fsutil.py:212-224`)先 `strip()` 再 `json.loads`,仍得 `None`。
|
|
69
|
+
- **observed**:`AttributeError: 'NoneType' object has no attribute 'get'`,**崩在排序键函数**(单元素列表也调键函数,`n=1` 即崩)。
|
|
70
|
+
- **崩溃链**:`MdCG.__init__`(`md_cg/mdcg.py:1028`)→ `_load_index`(修前 `HEAD:1067`,`for rec in ShardedLog.read_all(...)`)→ `fsutil.py:386`(修前)`recs.sort(key=lambda r: (r.get("_t", 0), r.get("_s", 0)))` → `<lambda>`。
|
|
71
|
+
- **站点**:`md_cg/fsutil.py:386`(`HEAD` 口径)。
|
|
72
|
+
|
|
73
|
+
### 腿 ③ 压缩重写(compact)
|
|
74
|
+
|
|
75
|
+
- **payload**:`null`。步骤:先在临时库 `add` 两个节点并 `close` 落快照 → `MdCG` 构造成功 → 把 `_index.json` 覆写为 `null` → 调 `compact_index`。
|
|
76
|
+
- **observed**:`AttributeError: 'NoneType' object has no attribute 'get'`。崩点是修前 `HEAD:1235` 的 `d.get`,**就在修前 `HEAD:1232` 起的 `try` 内**,而 `except` 在修前 `HEAD:1239` 只捕 `(ValueError, OSError)` ⇒ 该函数的降级标志(变量名 `scan_fallback`,修前 `HEAD:1230` 初始化、工作区 `md_cg/mdcg.py:1237` 初始化)在这类载荷下**保持 `False`**。
|
|
77
|
+
- **标志名与「恒」的适用范围(澄清)**:§1 表③ 与本文提到的是**同一个标志** `scan_fallback`——采集原文里的 `scan_fallback_reached=false` 是复现环节对该标志的原始观测记录,等价写法,本报告统一改用标识符原名。**「走不到」只对「顶层非对象」这一类载荷成立**,不是对所有损坏类型成立:修前同函数对 schema 不符(走 `else` 分支)与 `ValueError`/`OSError`(走 `except`)都能把标志置 `True`——本报告的**对照腿**就实测了 `ValueError` 那一支(写 `'{"broken": '`)能正常降级保池。
|
|
78
|
+
- **写盘状态**:崩溃发生在任何写盘之前——`_index.json` 仍为 `"null"`、盘面 2 个 `.md` 未动。即**本次调用没清池**,但该库自此落入腿① 同一形态(同文件同内容已在腿① 实测崩)而**永久不可装载**。
|
|
79
|
+
- **对照腿(证明降级机制存在、只是被绕过)**:同函数、同位置写 `'{"broken": '`(触发 `ValueError`)→ `compact_index` 正常返回且节点 `n225_ctrl_node` 保池。**降级机制确凿存在,被 `AttributeError` 绕过。**
|
|
80
|
+
- **站点**:`md_cg/mdcg.py:1235`(`HEAD` 口径)。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 3. 修法契约与落点
|
|
85
|
+
|
|
86
|
+
修法口径有界:**不新增第三套判据**,把非对象折进既有降级语义。
|
|
87
|
+
|
|
88
|
+
### 3.1 快照类型闸(装载面)
|
|
89
|
+
|
|
90
|
+
- `md_cg/mdcg.py:1057`:顶层非 `dict`(`null`/`[]`/`123`/`"abc"`)视同损坏 ⇒ `idx` 保持 `None` ⇒ 落到该函数**既有**的「回退全库扫描」路径(`md_cg/mdcg.py:1072-1073` 的 `idx = {"schema": SCHEMA, "nodes": self._scan_nodes(), "buckets": {}}`)。
|
|
91
|
+
- 同步改 `md_cg/mdcg.py:1043` 的生效条件注释,写明顶层非对象面。
|
|
92
|
+
|
|
93
|
+
### 3.2 compact 走既有 `scan_fallback`
|
|
94
|
+
|
|
95
|
+
- `md_cg/mdcg.py:1242-1249`:新增顶层非对象分支 → `scan_fallback = True`,与「schema 不符」(`1252-1253`)、「`ValueError`/`OSError`」(`1254-1255`)**同口径**。
|
|
96
|
+
- 落点仍走既有分支体 `md_cg/mdcg.py:1256-1262`(`idx = {"schema": SCHEMA, "nodes": self._scan_nodes(), "buckets": {}}`),随后 `_apply_log` / `_count_buckets` / `atomic_write` / `ShardedLog.clear` 原序不变。
|
|
97
|
+
- 同步改 `md_cg/mdcg.py:1233` 的生效条件注释。
|
|
98
|
+
|
|
99
|
+
### 3.3 分片非对象行:跳过且可观测
|
|
100
|
+
|
|
101
|
+
- 分流点 `md_cg/fsutil.py:436-444`(`ShardedLog.read_all` 循环内):`isinstance(rec, dict)` 进回放列表,非对象行进 `skips`,整片读成功才 `note_nonobject_rows(skips)`(半途 `PermissionError` 重试不重复计数);排序键 `md_cg/fsutil.py:451` 因此永不接触非 dict。
|
|
102
|
+
- 记账设施 `md_cg/fsutil.py:266-315`(插入块整体 50 行;下列定义在其中的 `276-313`):
|
|
103
|
+
- `NONOBJECT_ROW_SKIPS`(进程内累计条数)
|
|
104
|
+
- `NONOBJECT_ROW_SAMPLES`(有界样本 ≤ `_NONOBJECT_SAMPLE_CAP = 32`,元素为 `(分片路径, 行号, JSON 类型名)`)
|
|
105
|
+
- `note_nonobject_rows()`(`fsutil.py:282`)每条分片一行 stderr 汇总告警(分片名、本次条数、首条行号与类型名、累计条数、排查方向)
|
|
106
|
+
- `nonobject_row_stats()` / `reset_nonobject_row_stats()`(`fsutil.py:303` / `309`)只读访问器与基线重置
|
|
107
|
+
- 实测告警样例(本报告实跑 `--branch-baseline` 时 stderr 直接可见):
|
|
108
|
+
`[fsutil] ShardedLog.read_all 跳过 4 条非对象记录行(分片 9000-c0ffee00.log,首条 行2 类型=NoneType)——坏行不进索引重放;本次后进程内累计 12 条。排查方向:该分片被非本协议写入方污染,或发生过截断 / 粘连。`
|
|
109
|
+
- 同步改 `md_cg/fsutil.py:402` 的生效条件注释。
|
|
110
|
+
|
|
111
|
+
### 3.4 契约未动项(同样是交付面的一部分)
|
|
112
|
+
|
|
113
|
+
| 未动项 | 位置 | 依据 |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `_maybe_reload_index` **一行未改** | `md_cg/mdcg.py:1138-1189` | `git diff -U1 -- md_cg/mdcg.py` 无该函数 hunk(实跑);其 `except (OSError, ValueError)`(`1159`)未加宽——非对象经 `_load_index` 根因收口后不再抛,加宽即新增第三套判据并吞真实异常;守卫 A 组第 5 条(`test_n225_nonobject_load.py:491`)钉这一点 |
|
|
116
|
+
| `read_jsonl` 通用契约未动 | `md_cg/fsutil.py:212-224` | `git diff` 无该函数 hunk;E4 断言(`test_n225_nonobject_load.py:646`)正向证据:写入 `{"a":1}`/`null`/`[]`/`123`/`"abc"`/`{"a":2}` 后产出类型序列仍为 `dict/NoneType/list/int/str/dict` |
|
|
117
|
+
| tombstone 语义不变 | `md_cg/mdcg.py:1082-1085`(`_load_index`)、`md_cg/mdcg.py:1270-1272`(`_apply_log`) | `e is None → pop(nid)` 原样;E5 断言(`test_n225_nonobject_load.py:656`);C 组另有行为面断言:tombstone 记录在 `read_all` 返回列表中保留、重放后 `n2` 不复活 |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. 验证数字
|
|
122
|
+
|
|
123
|
+
### 4.1 本报告实跑(2026-09-29,工作目录 `<repo>`,`PYTHONUTF8=1` + `python -X utf8`)
|
|
124
|
+
|
|
125
|
+
> 时效声明:下表中的四项命令在**报告首次成稿的同一日**实跑(守卫正向 → 变异自证 → 定向套件 → `md_cg` 面全量回归,逐条见「尾部数字」列);**本次修订轮未重跑这四项**(修订轮只做只读核对与文档编辑,命令见附录)。四项的 `rc` 与尾部数字为当时的实跑记录。
|
|
126
|
+
|
|
127
|
+
| 命令(原样) | 尾部数字 | 退出码 |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `python -X utf8 -m md_cg.test_n225_nonobject_load` | `非对象形态索引/日志守卫(N225):56 通过,0 失败` | `0` |
|
|
130
|
+
| `python -X utf8 -m md_cg.test_n225_nonobject_load --branch-baseline` | `未变异基线:失败=0`;关掉装载面类型闸 → `红项=25,命中组={'A': 20, 'D': 5}`;关掉 compact 面类型闸 → `红项=16,命中组={'B': 16}`;关掉分片日志坏行过滤 → `红项=15,命中组={'C': 10, 'D': 5}`;`定点变异自证:PASS` | `0` |
|
|
131
|
+
| `python -X utf8 scripts/run_tests.py md_cg -k n225` | `PASS md_cg.test_n225_nonobject_load` / `SUMMARY 1/1 通过,0 跳过(依赖缺失/平台不符)` | `0` |
|
|
132
|
+
| `python -X utf8 scripts/run_tests.py md_cg` | `SUMMARY 190/190 通过,3 跳过(依赖缺失/平台不符)` | `0` |
|
|
133
|
+
|
|
134
|
+
- 守卫 56 条断言的构成(读码:`test_n225_nonobject_load.py:457-658`,与实跑尾行一致):A 装载面 4 形态 × 5 = **20**(`g_a`,`test_n225_nonobject_load.py:457`);B compact 面 4 × 4 = **16**(`g_b`,`503`);C 分片日志面 **10**(`g_c`,`534`);D stdio 端到端 **5**(`g_d`,`580`);E 源断言 + 不动契约 **5**(`g_e`,`617`)。合计 56。
|
|
135
|
+
- 变异预期表(读码:`test_n225_nonobject_load.py:677-691` 的 `_MUTATIONS`)与实跑红项**逐位一致**:`{"A": 20, "D": 5}` / `{"B": 16}` / `{"C": 10, "D": 5}`。锚点漂移按 fail-closed 退出码 `2`(`_branch_baseline`,`test_n225_nonobject_load.py:740-742`)。
|
|
136
|
+
- D 组跨进程面的注入机制(读码:`test_n225_nonobject_load.py:312-340` 的 `_SITECUSTOMIZE_SRC` + `350-361` 的 `PYTHONPATH` 注入):内存变异不跨进程,故经 `sitecustomize` 把同一处定点变异注入子进程,并以 stderr 标记 `[n225-mut] injected` 自证(标记缺失即 D 组判红)。
|
|
137
|
+
|
|
138
|
+
### 4.2 由本工作流采集、**未由本报告复跑**的数字
|
|
139
|
+
|
|
140
|
+
先给**执行者与命令**(本节数字全部由本工作流的脚本在世界层执行;命令文本取自工作流草稿 `.zcode/workflow-drafts/N225-索引日志非对象装载面类型闸.dwf.ts`,行号为该文件的行号):
|
|
141
|
+
|
|
142
|
+
| 采集项 | 实际执行的命令 | 采集结果 | 本报告是否执行 |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| python 全量 | `python -X utf8 scripts/run_tests.py`(`dwf.ts:179` 记其退出码) | 退出码 `0`;`===== SUMMARY 257/257 通过,5 跳过(依赖缺失/平台不符) =====` | **未复跑**(本报告只复跑 `md_cg` 面 `190/190`,未跑 hive 面) |
|
|
145
|
+
| 容器栈一 | `docker run --rm -v <repo>:/work -w /work -e CARGO_TARGET_DIR=/tmp/target rust:bookworm bash scripts/linux_verify.sh full`(`dwf.ts:185-190`) | 退出码 `0`;`=== smoke(D-2 Linux 口径:SIGTERM 收尾)===`、`[PASS] restart 后 serve 存活`、`结果: 21 pass / 0 fail`、`[PASS] smoke_test (linux)`、`=== 汇总: 38 pass / 0 fail ===` | **未执行**(本机 win32 无 Docker) |
|
|
146
|
+
| 容器栈二 | `docker run --rm -v <repo>:/work -w /work node:22-bookworm bash -c "npm install --include=dev && npm run build && node --import tsx --test test/*.test.ts"`(`dwf.ts:201-206`) | 退出码 `0`;`# pass 71`、`# fail 0`、`# cancelled 0`、`# skipped 3`、`# todo 0`、`# duration_ms 5364.33548` | **未执行**(同上) |
|
|
147
|
+
| 服务层 OLD 对照(复现环节 exp3) | stdio 子进程 `python -X utf8 -m md_cg.mcp_server`(同一库根写坏为非对象快照) | 退出码 `1`;应答 0 条;traceback 落 `md_cg/mdcg.py:1028` 的 `self.index = self._load_index()` → `AttributeError: 'list' object has no attribute 'get'` | **未复跑**(§0「rc=1」即引此条,不是 §2 三腿) |
|
|
148
|
+
| 修后独立复核(存活实例探活面 / 定点实验) | 复现臂与复核臂的进程内/临时 `sitecustomize` 注入实验(`exp1b` / `exp12` / `exp2` / `exp3`) | 总体 `ACCEPT`,依据见下 | 未复跑;本报告只做读码与守卫/回归的独立复跑(§4.1) |
|
|
149
|
+
|
|
150
|
+
**容器栈一两个计数的关系**:`结果: 21 pass / 0 fail` 是 `hive.hive_mcp.smoke_test` **自身**的输出;`=== 汇总: 38 pass / 0 fail ===` 是 `scripts/linux_verify.sh` 的 `record()` **总汇**——脚本计数器(`scripts/linux_verify.sh:11-15`)对 `cargo test`、各 python 套件、`cargo build --release`、`smoke_test`(`:102-104`)逐项 +1,最后在 `:107` 打印。故 **21 ⊆ 38**:21 是 smoke 子项的通过数,38 是全脚本各子项通过数之和。
|
|
151
|
+
|
|
152
|
+
**容器栈是否跑到 N225 的入口**:N225 的两条入口登记在 `scripts/linux_verify.sh:48`(python 套件循环含 `test_n225_nonobject_load`)与 `:86`(自证循环含 `test_n225_nonobject_load --branch-baseline`)。若那次栈一执行确为 `linux_verify.sh full` 且走到了最终汇总(`record` 只在每条命令返回后才计数),则两条入口**都被执行过**——这是「读码 + 采集尾行」的推断,本报告未执行容器、无法直接核对。**容器栈二跑的是 `test/*.test.ts`(node 面),不含 N225 的 python 入口**,其通过与否与本次修复无因果关系。
|
|
153
|
+
|
|
154
|
+
**「四态判定单」的名称与项数(澄清)**:采集原文写 `独立复核=ACCEPT,依据=【四态判定单】`,其后实列**五项**:①构造面 ACCEPT ②compact 面 ACCEPT ③分片日志面 ACCEPT ④stdio 端到端 ACCEPT ⑤守卫+变异自证 ACCEPT。本报告按实列的五项转述;名称「四态」与实列 5 项的不一致**存在于采集原文**,本报告未取得其解释,也不代为归并(不把第 5 项说成非「态」)。
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 5. 边界与未覆盖
|
|
160
|
+
|
|
161
|
+
### 5.1 未跑的验证(明确「没做」)
|
|
162
|
+
|
|
163
|
+
- **`npm run gate`(发布件/纪律门禁)未跑**。**为什么它依赖 `git add`——机制(读码所得,本报告未运行)**:
|
|
164
|
+
- `package.json` 的 `scripts.gate` 是一条链:`check_unreachable.py && check_publish_artifact.py && cogmap_sync.py check && link_check.py && workspace_index.py --check && verify_discipline.py --allow-missing`。
|
|
165
|
+
- 链中的 `scripts/check_publish_artifact.py` 有一条判据 **R4:包内非追踪件仅允许 `lib/`**(判据文字见脚本头 `scripts/check_publish_artifact.py:15`;实现常量 `ALLOW_NONTRACKED = ("lib/",)` 在 `:46`,核验面走 `git ls-files` 在 `:598`,判定输出在 `:604-605`)——即发布件清单里出现未被 git 追踪的文件就判红。
|
|
166
|
+
- 本次新增的 `md_cg/test_n225_nonobject_load.py` **当前确实不在追踪面上**:实跑 `git ls-files --error-unmatch md_cg/test_n225_nonobject_load.py` → `rc=1`,stderr 为 `error: pathspec 'md_cg/test_n225_nonobject_load.py' did not match any file(s) known to git`(对照:同一命令对 `md_cg/mdcg.py` 与 `package-lock.json` 均 `rc=0`)。⇒ 在 `git add` 之前跑 gate,R4 面必然命中。
|
|
167
|
+
- 「属写操作」也有独立依据:`npm pack` 会触发 `package.json` 的 `prepare` 生命周期脚本,而该脚本先 `fs.rmSync('lib',{recursive:true,force:true})` 再执行 `tsc -p tsconfig.json`(即**会写构建产物**)。
|
|
168
|
+
- **边界声明**:本报告既未 `git add`、也未运行 gate ⇒ 上面是机制说明,**不是「gate 会红」的实测结论**;「需先 `git add`」这一措辞同时是本工作流的自述(`.zcode/workflow-drafts/N225-索引日志非对象装载面类型闸.dwf.ts:280`)。**未跑,不能写成通过。**
|
|
169
|
+
- **`scripts/linux_verify.sh` 未跑**:本机 win32 无 Docker;两条 N225 入口已登记在脚本清单中(`scripts/linux_verify.sh:48` 的 python 套件清单含 `test_n225_nonobject_load`;`:86` 含 `test_n225_nonobject_load --branch-baseline`),并已补注释写明三处定点变异的预期红项数(25/16/15)与跨进程注入机制(`scripts/linux_verify.sh:75-79`,本次实跑 `git diff` 可见该 hunk)。**登记≠执行**:容器侧由哪条命令执行、是否跑到这两条入口,见 §4.2 的表与说明。
|
|
170
|
+
- 本报告**未复跑**修前三条腿取证(§2)与 §4.2 全部采集项。
|
|
171
|
+
|
|
172
|
+
### 5.2 TS 面与在役面
|
|
173
|
+
|
|
174
|
+
- TS/JS 面**零改动**:实跑 `git diff --numstat` 的三行分别是 `67 2 md_cg/fsutil.py`、`19 4 md_cg/mdcg.py`、`8 2 scripts/linux_verify.sh`——**没有** `.ts`/`.test.ts`/`package.json` 的文件。容器栈二(`node:22-bookworm`,命令见 §4.2 表,跑的是 `test/*.test.ts`)按**采集结果**通过,本报告未执行;且栈二与本次改动**无因果关系**(改动不含 TS 面),故它不构成对本次修复的验证——「TS 面零改动」这一半可由上面的 `git diff --numstat` 独立支撑,「栈二仍跑过」这一半只有采集来源、本报告无处复现。
|
|
175
|
+
- **活库 / DSH 日志零改动**:实跑 `git status --porcelain --untracked-files=all` 未出现 `data/` 下任何文件与 `scripts/dsh_log_index.py`;本次改动面不含在役数据面与 DSH 日志索引。全部实验库根在系统临时目录(`tempfile.mkdtemp`,跑完 `rmtree`),只用只读手段。
|
|
176
|
+
|
|
177
|
+
### 5.3 已知缺口(来源=本工作流独立复核的「缺口挑战」,**本报告未复跑其实测**)
|
|
178
|
+
|
|
179
|
+
类型闸的判据轴是**深度 1**;同族失败模式在深度 2 复发,且复发的三处都在本次守卫的断言面之外。**不确定度分层**:前三条(dict 行坏键型 / 载荷非对象 / `nodes` 内含非对象)与记账面诸条由复核环节标注为**已测**(medium/medium/low);**最后一行「同族第二组 9 处」来源方自标为「读码判断,未实测」**——本报告照录该标注,不升级为结论。凡「本报告未复跑」的,均指本报告没有重跑那些实验,不等于原实验无效。
|
|
180
|
+
|
|
181
|
+
| 严重度 | 位置 | 性质 |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| medium | `md_cg/fsutil.py:451` | 闸只认「行是 dict」,不认 `_t`/`_s` 可比:dict 行的 `_t` 为 str 或 `_t` float/str 混排时 `sorted` 抛 `TypeError`,异常逃出 `read_all`,落在 `_load_index`(`md_cg/mdcg.py:1074`)与 `_apply_log`(`md_cg/mdcg.py:1265`)的 `try` 之外 ⇒ 构造/compact 崩,与本次所修是同一条链。C 组只喂非对象行(`test_n225_nonobject_load.py:183`),无断言覆盖「dict 行坏键型」 |
|
|
184
|
+
| medium | `md_cg/mdcg.py:1087`、`md_cg/mdcg.py:1228` | `_count_buckets` 对条目 `e` 直接 `.get`:行是 dict 但载荷 `e` 非对象(如 `123`)时装载/compact 仍 `AttributeError`;重放循环(`1074-1087`)在 `try` 之外,`_maybe_reload_index` 的 `except`(`1159`)只收 `OSError`/`ValueError`,接不住 |
|
|
185
|
+
| low | `md_cg/mdcg.py:1057` | 闸只查顶层:快照是 dict 但 `nodes` 内含非对象条目时,`_count_buckets`(`md_cg/mdcg.py:1228`)等消费点仍裸 `.get`。**触发条件(把「指纹对齐」说清)**:该快照的 `_fingerprint`(目录树 mtime 指纹)与当前 `self._dir_fingerprint()` **相等**(判定见 `md_cg/mdcg.py:1069-1071`)⇒ 顶层 `isinstance` 闸放行、指纹闸也放行 ⇒ **不落回全库扫描**,直接以该快照为基底,随后 `_count_buckets`(在 `md_cg/mdcg.py:1088` 被调用)遍历到非对象条目即崩。这里的「指纹对齐」专指 `_fingerprint` 这一条,**不是**「schema 匹配」 |
|
|
186
|
+
| medium | `md_cg/fsutil.py:276-313`、`443`、`md_cg/mdcg.py:1290` | 记账面无生产消费者、不落盘:计数与样本只在进程内,重启归零;无落盘账本,health/审计/工具面都不读它 ⇒ 生产可见面只有 stderr 一行;而 `compact_index` 重放后无条件 `ShardedLog.clear` 删掉被污染分片,连这一行物证也不可回溯。故「是否掩盖真实写入丢失」在耐久口径上仍是『是』 |
|
|
187
|
+
| medium | `md_cg/fsutil.py:223-224` vs `437-441` | 跳过面两类不对称:非法 JSON(截断/粘连)在 `read_jsonl` 的 `except ValueError: continue` 就被吞掉,`read_all` 根本看不见 ⇒ 不计数、不告警;真正由写入中断产生的丢行仍完全静默。且记账不含「期望条数 vs 应用条数」对照,无法区分『日志混进坏行』与『写入本就没发生』 |
|
|
188
|
+
| medium | `md_cg/mdcg.py:1161-1165` | 与 **N230** 的互动:非对象快照把「崩」改写成「静默 reload + `_dirty` 重放」,为 N230 新增一条入口(不是新机制)。**N230 是什么(指路)**:本仓缺陷台账 `docs/eval/缺陷挖掘_自主迭代_v25.md` 记为「generation 区/修复期残留:`_dirty` 重放遮蔽」,站点 `md_cg/mdcg.py:1073-1078`,severity medium,处置记为**留档(未修)**(见该文件 `:102`、`:132`、`:178`);本报告不展开其内容,仅据采集原文引用 |
|
|
189
|
+
| low | `md_cg/mdcg.py:1072-1073` | 回退全库扫描无「已判定损坏」标记:只要 `_index.json` 仍是非对象,每个新实例构造都要全扫一次,直到某写者 compact 重写好快照;常驻/只读形态(不 close)会持续付 |
|
|
190
|
+
| low | `md_cg/mdcg.py:1256-1262`、`1290` | 变宽的既有危险:`scan_fallback` 分支以 `_scan_nodes()` 为基底且不计入 `_count_md_files` 对账;扫描结果为空时写出空池快照并清掉全部分片。该行为对 `ValueError` 类损坏是既有的(`HEAD` 同一分支),本次把非对象折进同一口径 ⇒ 触发面变宽 |
|
|
191
|
+
| medium | `md_cg/test_n225_nonobject_load.py:616-657`、`677-691`、`scripts/linux_verify.sh:13-15` | 守卫判别力:三处变异红项数恰好 25/16/15(实测达标),但 **E 组 5 条「源文本在场」断言不在任何预期红项表里**——整组换空后 `--branch-baseline` 仍 PASS、rc=0(PASS 56→51),而 `linux_verify.sh:13-15` 的 `record()` 只判退出码、`run_tests.py:203-221` 同样只看 rc ⇒ 删断言无任何机械面可发现;E1 判据字符串与实现行字面量逐字相同,属源码文本级自证,无行为判别力 |
|
|
192
|
+
| medium | `md_cg/mdcg.py:3773`、`3522`、`3529`、`3497`、`md_cg/conformance.py:263`、`266`、`291`、`md_cg/consolidate.py:1166`、`md_cg/mdcos.py:2415`、`2420`、`md_cg/mreview/candidates.py:126`、`138` | 同族未修面(本次显式声明不改 `read_jsonl` 通用契约,故这些调用方在非对象行下仍裸崩):`_access.log` 的 `access_counts`、`_reflection.jsonl`、conformance 的三个注入点、`_maintain.jsonl`、`_deletions.jsonl`、inbox/decisions 四源候选 `generate()` —— 全线同一个 `AttributeError`(复核环节给了逐条实测证据) |
|
|
193
|
+
| low | `md_cg/crosscheck.py:929`、`md_cg/refine.py:110`、`md_cg/mreview/govern.py:442`、`md_cg/backfill.py:486`、`md_cg/mdcos.py:407`、`413`、`md_cg/metacognition.py:527`、`md_cg/mdcos.py:2368`、`2411` | 同族第二组:**读码判断,未实测**(复核环节显式标注为「怀疑」)。前四处对 `read_jsonl` 结果直接 `.get`,非对象行必然 `AttributeError`;后几处只是裸返回非对象行,不崩,但会把 `null` 泄漏进工具输出与后续消费方 |
|
|
194
|
+
|
|
195
|
+
同时存在的**正向边界**(同源采集):类型闸对 dict 恒真 ⇒ 正常快照不会被误判(1500 节点合法快照装载 7.3 ms 且不触发 `_scan_nodes`);崩溃面确实收窄;`close` 后快照顶层自愈为 dict。
|
|
196
|
+
|
|
197
|
+
### 5.4 台账自检与现场的一处不一致
|
|
198
|
+
|
|
199
|
+
工作流「触碰面自检」记为「`git status` 仅 `md_cg/fsutil.py`、`md_cg/mdcg.py`、`scripts/linux_verify.sh`(改)+ `md_cg/test_n225_nonobject_load.py`(新)」;本报告实跑 `git status --porcelain` 额外见到 **`M package-lock.json`**。该文件与 N225 修复面无关(本次改动全在 Python 侧与验证脚本),本报告**不主张其归属**,仅记录现场。
|
|
200
|
+
|
|
201
|
+
`package-lock.json` 的 `M` 与 `git diff` 的实测关系(消除「3 files 还是 4 files」的歧义):
|
|
202
|
+
|
|
203
|
+
| 命令(均为本次实跑) | 输出 |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `git status --porcelain` | 含 ` M package-lock.json` |
|
|
206
|
+
| `git diff --stat`(**不带**路径参数) | `3 files changed, 94 insertions(+), 8 deletions(-)`,**未列** `package-lock.json` |
|
|
207
|
+
| `git diff --numstat`(**不带**路径参数) | 只有三行:`67 2 md_cg/fsutil.py`、`19 4 md_cg/mdcg.py`、`8 2 scripts/linux_verify.sh` |
|
|
208
|
+
| `git diff --stat -- package-lock.json` / `git diff --numstat -- package-lock.json` | 均为**空输出**(rc=0) |
|
|
209
|
+
|
|
210
|
+
⇒ 「只覆盖三个文件」不是靠选参数凑出来的结论:**全量** `git diff`(不带路径)本身就只输出三个文件,`package-lock.json` 的内容 diff 为空。可能的成因是该文件只有行尾(CRLF)层面的差异、被内容比较规范化掉(`git status` 与 `git diff` 在此类情形下口径不同,且本次实跑 `git diff` 时确实出现过 `LF will be replaced by CRLF` 的告警);**本报告未进一步取证这一成因**,只记录上述四条实测输出。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 6. 诚实留痕(哪些验证没做)
|
|
215
|
+
|
|
216
|
+
1. **`npm run gate` 未跑** —— 前置需 `git add`(写操作),与硬边界冲突,留主会话收口。**未跑,不写成通过。**
|
|
217
|
+
2. **容器两栈(`rust:bookworm` / `node:22-bookworm`)未由本报告复跑** —— 本机 win32 无 Docker;§4.2 数字是本工作流采集的结果,不是本报告的执行结果。
|
|
218
|
+
3. **`scripts/linux_verify.sh` 未执行** —— 同上;只核对了它已登记两条 N225 入口(`scripts/linux_verify.sh:48`、`:86`)与变异预期注释(`75-79`);执行该脚本的容器命令、以及两条入口是否真被跑到,见 §4.2。
|
|
219
|
+
4. **python 全量 257/257 未由本报告复跑** —— 本报告实跑的是 `scripts/run_tests.py md_cg`(190/190 通过、3 跳过、rc=0)与 `-k n225`(1/1、rc=0);hive 面未跑。
|
|
220
|
+
5. **修前三条腿(§2)与独立复核定点实验未由本报告复跑** —— 均引用本工作流采集;本报告在守卫与回归面做了独立复跑(§4.1)。
|
|
221
|
+
6. **§5.3 的缺口实测未由本报告复跑** —— 其中「同族第二组」来源方自己已标注为**读码判断、未实测**;本报告照录该标注,不升级为结论。
|
|
222
|
+
7. **未 `git add` / `commit` / `push`;未改任何源码或其它文件** —— 本报告仅新增本文件一份。
|
|
223
|
+
8. **`package-lock.json` 的改动归属不明** —— 见 §5.4,本报告不作推断。
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 7. 读者问题的处置:能给的依据与裁定归属
|
|
228
|
+
|
|
229
|
+
读者另提 8 个后续问题。处置原则:**能补依据的补依据(标注实测/读码)、属排期或裁定或改代码的标明归属**。裁定权归使用者,本报告不代裁,也不把未定事项写成已定。
|
|
230
|
+
|
|
231
|
+
| # | 问题 | 本报告能给的依据 | 归属 |
|
|
232
|
+
|---|---|---|---|
|
|
233
|
+
| 1 | `npm run gate` 由谁、何时跑?没跑过算不算完成? | 机制面已补(§5.1):`package.json` 的 `scripts.gate` 链、R4「包内非追踪件仅允许 `lib/`」(`scripts/check_publish_artifact.py:15`、`:46`、`:598`、`:604-605`)、`git ls-files --error-unmatch md_cg/test_n225_nonobject_load.py` 实测 `rc=1`。**时间与责任人本报告未取得**;「以 gate 绿为完成条件」属门禁定义 | 使用者裁定 |
|
|
234
|
+
| 2 | §5.3 十余处同族未修面何时修、是否另开编号? | 事实面:本次显式声明不改 `read_jsonl` 通用契约(§3.4);N225 自身在缺陷台账里被列为「留档未修 3 项」之一(`docs/eval/缺陷挖掘_自主迭代_v25.md:13`),编号纪律见该文件 `:11`(自 N221 续编、全局唯一;`N230` 即一次并号改判)。**排期与开号决定本报告未取得** | 使用者裁定 |
|
|
235
|
+
| 3 | 深度 2 的三个缺口本次不修,裁定依据是风险可接受还是排期在后? | 只有事实面:三处 severity 分别 medium/medium/low(§5.3);修法自述「不新增第三套判据」(`md_cg/mdcg.py:1055-1056`、`md_cg/fsutil.py:423-425` 的注释)。**「风险可接受」还是「排期在后」在采集材料里没有对应记录——本报告未取得该裁定,不代补** | 使用者裁定(依据缺失) |
|
|
236
|
+
| 4 | 记账面要不要落盘、要不要暴露给 health / 工具面? | 事实面(**本报告实跑复核过**):对全树(排除 `.git`/`node_modules`/`target`/`__pycache__`)grep `NONOBJECT_ROW_SKIPS\|NONOBJECT_ROW_SAMPLES\|note_nonobject_rows\|nonobject_row_stats\|reset_nonobject_row_stats`,命中文件只有 `md_cg/fsutil.py`(19 处,定义与实现)、`md_cg/test_n225_nonobject_load.py`(5 处,守卫)与本报告自身——**无第三种消费方**。新增消费者的改动面本报告未评估 | 使用者裁定 |
|
|
237
|
+
| 5 | `scan_fallback` 空扫描会写空池快照并清分片,要不要加保护? | 事实面(读码):降级分支不做「扫描数 vs 盘面数」对账——对账条件是 `if not scan_fallback and self._count_md_files() != len(idx["nodes"])`(`md_cg/mdcg.py:1277`),**只在非降级路径**执行;降级分支直接以 `self._scan_nodes()` 为基底(`md_cg/mdcg.py:1261-1262`),随后无条件 `ShardedLog.clear`(`md_cg/mdcg.py:1290`)。这正是 §5.3 那条 low 风险的机制 | 使用者裁定(本报告不改代码) |
|
|
238
|
+
| 6 | E 组源断言整组换空后门禁仍 PASS,要不要给断言面加机械守卫? | 事实面:`scripts/linux_verify.sh:13-15` 的 `record()` 只判退出码;`run_tests.py` 同样只看 rc(采集给的行号)⇒ **当前没有「断言数不得减少」的机械面**;守卫自身的 `_MUTATIONS` 预期表(`test_n225_nonobject_load.py:677-691`)不含 E 组 | 使用者裁定 |
|
|
239
|
+
| 7 | `package-lock.json` 是谁改的、要不要一起提交、会不会影响 gate? | 实测四条见 §5.4(`status` 记为已跟踪的 `M`、`git diff` 内容为空;成因未取证)。**归属未知,本报告不推断**。对 gate 的影响:R4 面针对「包内非追踪件」,而该文件**已被 git 追踪**(`git ls-files --error-unmatch package-lock.json` 实测 `rc=0`)⇒ 不落在 R4 的非追踪件面上;但 gate 未运行 ⇒ **不能给出「不影响」的结论** | 归属与提交=使用者;「不影响 gate」本报告不下结论 |
|
|
240
|
+
| 8 | 只读/常驻形态下快照长期非对象,要不要启动时自愈? | 事实面(采集 + 读码):非对象快照下每次构造都会全扫(`md_cg/mdcg.py:1072-1073`);`close()` 会写回合法快照(§5.3 正向边界记的「自愈」)。「启动时写回」的写面与并发语义(多实例/锁)本报告未评估 | 使用者裁定 |
|
|
241
|
+
|
|
242
|
+
**明确未代裁的点**:第 1、3、5、6、8 条都涉及「改不改代码 / 加不加守卫 / 何时做」,本次硬边界是「只改本报告文件、不得动源码」,故一律只给现状依据,不做实现。
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 附:本报告执行的动作留痕
|
|
247
|
+
|
|
248
|
+
| 动作 | 命令 / 手段 | 性质 |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| L1 直跑:守卫正向 | `python -X utf8 -m md_cg.test_n225_nonobject_load` | 只读(临时目录合成库;不改仓库/外部状态)— 低频·可逆 |
|
|
251
|
+
| L1 直跑:变异自证 | `python -X utf8 -m md_cg.test_n225_nonobject_load --branch-baseline` | 同上 |
|
|
252
|
+
| L1 直跑:定向套件 | `python -X utf8 scripts/run_tests.py md_cg -k n225`、`python -X utf8 scripts/run_tests.py md_cg` | 同上 |
|
|
253
|
+
| 只读核对 | `git status --porcelain`、`git diff --stat`、`git diff --numstat`、`git show HEAD:md_cg/mdcg.py`、`git show HEAD:md_cg/fsutil.py`、`Read` 若干 | 只读 |
|
|
254
|
+
| 只读核对(v1.0-r1 修订轮) | `git diff -U0 -- md_cg/mdcg.py scripts/linux_verify.sh`、`git diff --numstat -- package-lock.json`、`git status --porcelain -- package-lock.json`、`git ls-files --error-unmatch <三个文件>`、读取 `package.json` 的 `scripts`、`scripts/check_publish_artifact.py`、`.zcode/workflow-drafts/N225-索引日志非对象装载面类型闸.dwf.ts`、`docs/eval/缺陷挖掘_自主迭代_v25.md` | 只读(未重跑测试;未改源码) |
|
|
255
|
+
| 落盘 | 本文件(`docs/eval/` 下唯一新增) | 唯一写操作 |
|
|
256
|
+
|
|
257
|
+
**纪律声明**(按工作纪律 0.2,未输出即视为未执行):
|
|
258
|
+
|
|
259
|
+
- 按工作纪律第 4 条: 根因纪律——先取证现场不猜测; 根因找到并验证后归档认知图并补不适用条件。
|
|
260
|
+
- 按工作纪律第 5 条: 验证纪律——入库前必须过回放/断言/回归验证, 未验证不固化。
|
|
261
|
+
- 按工作纪律第 8 条: 中文思考——中文书写; 复杂(新奇/挑战/曾受批评/高信息差)→「我们需要」交流确认互补盲区; 简单→「让我」快速执行不空耗; 每段推理开篇用中文短语锚定语言。
|
|
262
|
+
- 按工作纪律第 17 条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道, 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。
|
|
263
|
+
- 按工作纪律第 18 条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。(本次为已明确路径的单文件读写操作,属该条「不适用」面。)
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 8. 补强批(v1.1):深度 2 两条残余收口 + 守卫 E 组判别力重排
|
|
268
|
+
|
|
269
|
+
> 本节是 v1.0 正文之后的**追加**段,v1.0 正文(§0–§7 与附录)**一字未改**。§5.3 中被本节收口的行,状态以本节为准。
|
|
270
|
+
> 改动面(本节实跑 `git status --porcelain -uall` 与 `git diff --numstat`):`M md_cg/fsutil.py`(+126/−13)、`M md_cg/mdcg.py`(+43/−21)、`M md_cg/test_n225_nonobject_load.py`(+433/−112)、`M scripts/linux_verify.sh`(+8/−0)(另有与本次修复无关的 `M package-lock.json`,口径见 §5.4)。未 `git add` / `commit` / `push`。
|
|
271
|
+
> 本节所引行号一律注明口径:**工作区行号**=读码当时的工作区文本;**HEAD 口径**=`8358fda4` 的 `git show HEAD:…` 文本(即 v1.0 已修、补强未修之前的状态)。
|
|
272
|
+
|
|
273
|
+
### 8.1 补强范围(三条)
|
|
274
|
+
|
|
275
|
+
| # | 面 | v1.0 的登记处 | 本轮状态 |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| ① | **排序键型不可比**:分片记录是 dict,但 `_t` / `_s` 不是数值 ⇒ 排序键抛 `TypeError` | §5.3 第 1 行(medium,`md_cg/fsutil.py:451` HEAD 口径) | 收口(§8.3.2),正/反向判别力由 8 处变异中的第 6 处钉死 |
|
|
278
|
+
| ② | **载荷 `e` 非 dict**:行是 dict 而载荷坏型,下游 `_count_buckets` 裸调 `.get` | §5.3 第 2 行(medium,`md_cg/mdcg.py:1087`/`1228` HEAD 口径) | 收口(§8.3.3),由第 4、5 处变异从「过窄/过宽」两侧钉死 |
|
|
279
|
+
| ③ | **守卫 E 组判别力**:5 条「源文本在场」断言不在任何预期红项表里,整组换空后 `--branch-baseline` 仍 PASS | §5.3 第 8 行(medium) | 重排(§8.3.4):整组删除、改行为断言、八处变异各自「恰好命中」 |
|
|
280
|
+
|
|
281
|
+
### 8.2 修前的两条独立复现证据(本工作流缺陷复现环节采集;**本节未复跑**)
|
|
282
|
+
|
|
283
|
+
两条腿都在系统临时目录的合成库上跑,跑完 `rmtree`,只用只读手段。采集原文的站点行号是 **HEAD 口径**,本节用 `git show HEAD:` 逐行核对无误。
|
|
284
|
+
|
|
285
|
+
**腿 ① 排序键型不可比**(采集标记 `crashed=true`)
|
|
286
|
+
|
|
287
|
+
- **payload**:
|
|
288
|
+
```
|
|
289
|
+
{"id":"x","_t":"abc"}
|
|
290
|
+
{"id":"y","_t":1.0}
|
|
291
|
+
----(_s 组,需 _t 相等才命中)----
|
|
292
|
+
{"id":"x","_s":"abc","_t":1.0}
|
|
293
|
+
{"id":"y","_s":1,"_t":1.0}
|
|
294
|
+
```
|
|
295
|
+
- **observed**:`TypeError: '<' not supported between instances of 'float' and 'str'`(`_t` 组);`TypeError: '<' not supported between instances of 'int' and 'str'`(`_s` 组)。`ShardedLog.read_all` 直调与 `MdCG(root)` 构造**两条入口同点抛出**。
|
|
296
|
+
- **崩溃点**:`md_cg/fsutil.py:451`(HEAD 口径),异常末帧即 `recs.sort(key=lambda r: (r.get("_t", 0), r.get("_s", 0)))`。本节实跑 `git show HEAD:md_cg/fsutil.py | sed -n '451p'` 核对该行为上式原文,与采集一致。
|
|
297
|
+
- **诚实边界(采集原文照录)**:`_s` 组若按字面给 `_t` 各异(1.0/2.0)则 **NO EXCEPTION**——元组第一元已决出胜负、第二元不被比较,必须令 `_t` 相等才能把 `_s` 型不可比暴露出来;该遮蔽变体实测**未崩**。(守卫 F1 因此把「旧键直排在同一批记录上必抛」写成断言的一部分,而不是只断言新实现不抛。)
|
|
298
|
+
|
|
299
|
+
**腿 ② 载荷 `e` 非 dict**(采集标记 `crashed=true`)
|
|
300
|
+
|
|
301
|
+
- **payload**:`{"id":"zz","e":123,"_t":1.0,"_s":1}`
|
|
302
|
+
- **observed**:`AttributeError: 'int' object has no attribute 'get'`。两条路径**同点**:
|
|
303
|
+
1. `MdCG(root)` 构造——`_load_index` 读分片日志、把 `e=123` 塞进 `idx["nodes"]`、随即调 `_count_buckets`;
|
|
304
|
+
2. 存活实例的 `cg.compact_index()`——同一分片第二次重放同样塞入、同样调 `_count_buckets`。
|
|
305
|
+
- **崩溃点**:`md_cg/mdcg.py:1228`(HEAD 口径),即 `_count_buckets` 里的 `b = e.get("bucket")`。本节实跑 `git show HEAD:md_cg/mdcg.py` 核对:`HEAD:1228`=`b = e.get("bucket")`;同法核对 `HEAD:1087`=`idx["nodes"][nid] = e`、`HEAD:1088`=`idx["buckets"] = self._count_buckets(idx["nodes"])`、`HEAD:1287`=compact 面同一句、`HEAD:1264-1274`=compact 面内嵌的重放闭包(即 §8.3.3 删除的那一份)。
|
|
306
|
+
|
|
307
|
+
### 8.3 改动点与机制
|
|
308
|
+
|
|
309
|
+
#### 8.3.1 记账面扩展(`md_cg/fsutil.py`)
|
|
310
|
+
|
|
311
|
+
| 落点(工作区行号,本节读码) | 内容 |
|
|
312
|
+
|---|---|
|
|
313
|
+
| `fsutil.py:266-291`(块注释)+ `286` / `288` / `290` / `292` | **同一记账面** `NONOBJECT_ROW_SKIPS` 由「非对象行」一类扩为**三类合计**;另设两个子面计数 `BAD_PAYLOAD_ROW_SKIPS`(`288`) / `BAD_SORTKEY_ROW_SKIPS`(`290`) 与各自样本容器;样本上限 `_NONOBJECT_SAMPLE_CAP = 32`(`292`) 不变(记账有界,不随坏行线性涨) |
|
|
314
|
+
| `fsutil.py:327-354` `note_bad_payload_rows(skips)` | 载荷坏型:条数进合计 + 子面,样本 `(来源, 记录 id, 载荷类型名)`,stderr 一行汇总;**空序列立即返回、不动任何计数** |
|
|
315
|
+
| `fsutil.py:362-388` `note_bad_sortkey_rows(skips)` | 排序键坏型:条数进同一合计 + 子面,样本 `(分片路径, 行号, (t 类型名, s 类型名))`,stderr 一行汇总;**只记账、不改记录**(记录不丢) |
|
|
316
|
+
| `fsutil.py:356` / `390` / `396` | 只读访问器 `bad_payload_row_stats()` / `bad_sortkey_row_stats()`;`reset_nonobject_row_stats()` 改为清三类计数与三个样本列表 |
|
|
317
|
+
| `fsutil.py:296` `note_nonobject_rows()` | 既有的非对象行记账点未改语义;其 docstring 与「生效条件」同步为「三类合计 + 只含非对象行样本」 |
|
|
318
|
+
|
|
319
|
+
三类 kind 名:**非对象行 / 载荷坏型 / 排序键坏型**——三者排查方向不同,故合计之外另留子面。
|
|
320
|
+
|
|
321
|
+
#### 8.3.2 排序键健壮化(`fsutil.ShardedLog.read_all`,契约①)
|
|
322
|
+
|
|
323
|
+
- `fsutil.py:535-536` 内嵌**单槽归一** `_norm(v)`:`v` 为 `int`/`float`(含 bool)且 `v == v`(非 NaN)时原样返回,其余(`str`/`list`/`dict`/`None`/NaN)一律记 `0`——与既有「缺键取 0」同一口径,**不新造第二套默认值**。
|
|
324
|
+
- `fsutil.py:564` 排序键改为 `(_norm(_t), _norm(_s))`;行分流仍为 `if not isinstance(rec, dict): … continue`(留出记录位),并**逐行检测「归一结果 ≠ 原值对」**即收进 `badkeys`,整片读成功后调 `note_bad_sortkey_rows(badkeys)`(`556`)——半途 `PermissionError` 重试**不重复计数**(与既有非对象行记账同一纪律)。
|
|
325
|
+
- 生效条件注释(`fsutil.py:493`)与 `read_all` docstring 同步,含 NaN 边界与次序不变性的文字论证。
|
|
326
|
+
|
|
327
|
+
#### 8.3.3 重放面收成单实现(`md_cg/mdcg.py`,契约②)
|
|
328
|
+
|
|
329
|
+
- 新增 `MdCG._apply_log(nodes)`(`mdcg.py:1079`,生效条件注释 `1078`):无 id 跳过;`str` 归一路径保留(legacy P3 自愈腿);`e is None` ⇒ `pop`(**tombstone 语义一字不改**);`e` 非 dict 且非 None ⇒ **跳过**并经 `fsutil.note_bad_payload_rows` 记账(`1120`);其余覆盖。docstring 明写「不新增第三套判据(`_count_buckets` 不加闸)」与范围边界(`_maybe_reload_index` 的 `_dirty` 面不在本次范围)。
|
|
330
|
+
- **装载面** `_load_index`:原内嵌重放循环删除,改调 `self._apply_log(idx["nodes"])`(`mdcg.py:1074`);生效条件注释同步(`1043`)。
|
|
331
|
+
- **compact 面** `compact_index`:删除内嵌的同源闭包,两处调用点改 `self._apply_log(...)`(`mdcg.py:1298`,以及计数对账触发重扫后的 `1307`);生效条件注释同步(`1265`)。
|
|
332
|
+
- 模块引入 `note_bad_payload_rows`(`mdcg.py:30-31`)。
|
|
333
|
+
- 机制收益:此前两面各写一份同源循环,「同口径」只是一句人眼约定;收成一处后**改一处即两处生效**——「两处同口径」由单实现保证,而非由约定保证。
|
|
334
|
+
- 实跑 `git diff -U0 -- md_cg/mdcg.py | grep "^@@"` 的 hunk 头:`@@ -31 +31 @@`、`@@ -1043 +1043 @@`、`@@ -1073,0 +1074,28 @@`、`@@ -1081,0 +1110,4 @@`、`@@ -1085 +1117 @@`、`@@ -1087,3 +1119,3 @@`、`@@ -1233 +1265 @@`、`@@ -1264,13 +1296,3 @@`、`@@ -1285 +1307 @@`——**不含 `_maybe_reload_index`(`1170`)一段**,即该函数本轮仍未动(见 §8.6 第 4 项)。
|
|
335
|
+
|
|
336
|
+
#### 8.3.4 守卫重排(`md_cg/test_n225_nonobject_load.py`,契约③④)
|
|
337
|
+
|
|
338
|
+
- **整组删除**原 E 组 5 条「源文本在场」断言及其源文本自证设施(`_src_mdcg` / `_src_fsutil` / `_window` 等)。
|
|
339
|
+
- **新增**四个 fixture `_case_payload`(`346`) / `_case_sortkey`(`408`) / `_case_tombstone`(`471`) / `_case_jsonl`(`492`) 与四组断言:E 载荷坏型收口(`827`)· F 排序键健壮化(`867`)· G tombstone 语义(`906`)· H `read_jsonl` 通用面(`929`);组表 `_GROUPS`(`947`)。
|
|
340
|
+
- C 组由 **10 条改 9 条**(`g_c`,`748` 起)——原 C 组里的「重放面 tombstone 语义」归 G 组,故 C 组只留日志面自己的判据(「一组一判据」是「变异红项恰好等于该组断言数」成立的前提)。
|
|
341
|
+
- `_MUTATIONS`(`971`) 由 **3 处扩到 8 处**;变异机制扩展为支持 fsutil 模块级函数(`which="fsutil_mod"`)并保 `staticmethod` 语义;D 组跨进程的 `sitecustomize` 注入同步扩展。
|
|
342
|
+
- docstring 重写,写入「组↔判据一一对应」与**旧 E 组判据的迁移映射**(旧 E1→A、E2→B、E3→C、E4→H、E5→G)。
|
|
343
|
+
- 本节实跑 `grep -n "win_load" md_cg/test_n225_nonobject_load.py`:**仅 1 处命中**(`test_n225_nonobject_load.py:69` 的模块头注历史说明),无断言消费它;`inspect.getsource` 只用于变异注入与取源。
|
|
344
|
+
|
|
345
|
+
#### 8.3.5 `scripts/linux_verify.sh`
|
|
346
|
+
|
|
347
|
+
`80-88` 的「变异红项表」注释从「三处定点变异(25/16/15)」更新为**八处**(含每处精确红项数)。实跑 `git diff --numstat` 显示该文件为 `8 0` ⇒ **仅注释、未改任何命令**。
|
|
348
|
+
|
|
349
|
+
#### 8.3.6 坏型可观测方式(「容忍 ≠ 静默」)
|
|
350
|
+
|
|
351
|
+
三类坏型的可观测面完全同构:①条数进**同一合计** `NONOBJECT_ROW_SKIPS`;②另进各自子面计数;③进各自有界样本(≤32);④各写一行 stderr 汇总(含来源基名、本次条数、首条定位信息与类型名、进程内累计);⑤有机器可读只读接口:`nonobject_row_stats()` / `bad_payload_row_stats()` / `bad_sortkey_row_stats()`。
|
|
352
|
+
|
|
353
|
+
- 守卫把文案钉进断言:E4(子面 3 条 + `stderr` 含「载荷坏型记录」「3 条」)、F4(4 条 + 样本)、F5(`stderr` 含「排序键坏型 4 条」、分片名 `9999-badkey.log`、`_t=str`)。
|
|
354
|
+
- **判别力反证**:变异④与⑥删掉判据后计数归 0 且 stderr 无输出 ⇒ 对应断言转红(**6/6**、**5/5**)——「不静默」本身可被机械钉死,不是宣言。
|
|
355
|
+
- 本节实跑直见的 stderr(`--branch-baseline`):`[fsutil] ShardedLog.read_all 跳过 4 条非对象记录行(分片 9000-c0ffee00.log,首条 行2 类型=NoneType)——坏行不进索引重放;本次后进程内累计 8 条。…`;排序键坏型那一行由 F5 的判据面钉住(本节该条 `[ok]`,格式串在 `fsutil.py:382-388`)。
|
|
356
|
+
- 采集方给的参考量级(原文照录):`[fsutil] 分片重放跳过 3 条载荷坏型记录(e 非 dict 且非 None,来源 _index_log,首条 id=p1 载荷类型=list)…本次后进程内累计 3 条(含非对象行)`。
|
|
357
|
+
|
|
358
|
+
#### 8.3.7 合法记录的排序**逐位不变**——证明(三条互相独立)
|
|
359
|
+
|
|
360
|
+
1. **直接跑改前实现对照**(采集方执行):用 `git show HEAD:md_cg/fsutil.py` 抽出旧 `read_all` 源码(旧排序键原文已打印核对)exec 进 fsutil 命名空间,与工作区新实现跑**同一批真实分片记录**(`ShardedLog.append` 生产写点产出:两个写者 = 两个分片、含并列 `_t`、跨写者重叠 `_s`、乱序写入):**合法批次 12 条 `old == new`(序与内容逐位全等**,如 ids `['a','b','c','d','i','j','k','l','e','f','g','h']`);**混合批次**(并入 `str`/`list`/`None`/NaN 坏型键行)旧实现必抛 `TypeError: '<' not supported between instances of 'str' and 'float'`、新实现不抛,且新实现的合法记录子序列(去掉混合目录特有的缺键行后)与旧实现在合法批次上的产出逐位全等;**记录一个不丢**(12 合法 + 5 坏型键 = 17)。
|
|
361
|
+
2. **守卫 F2 常驻断言**(65 条之一):真实分片批次上「新实现返回列表的合法记录子序列 == **旧键 oracle**(内建 `sorted` + 旧键表达式,稳定排序,同一收集序)」逐条比对序与内容。本节实跑该条为 `[ok]`(见 §8.4 守卫正向尾行)。
|
|
362
|
+
3. **机制论证 + 反向自证**:合法槽 `_norm(v)` 恒返回 `v` ⇒ 排序键逐位等于旧键 ⇒ 同一稳定排序算法在同一输入序列上产出同一次序。反向自证:变异⑥把归一谓词改成 `True`(等价于还原旧键直排)后 fixture **立即 `TypeError`** ⇒ 转红(本节实测红项=5,命中 F 组)——说明该 fixture 真的覆盖了坏型,而非空转。
|
|
363
|
+
4. **独立复核的机械对照**(采集):纯合法 fixture(两分片、含 `_t=1000.0` 并列 4 条、跨分片 `_s` 重叠)下 **HEAD 版 `read_all` 与现行 `read_all` 输出逐条全等**(ids `['g','c','a','f','b','h','d','e']`,对象级 `==` 为真),且与「按收集序 + 改前旧键 `sorted`」的独立 oracle 全等;该轮自跑守卫 F2 亦为 `[ok]`。
|
|
364
|
+
|
|
365
|
+
### 8.4 补强后的验证数字
|
|
366
|
+
|
|
367
|
+
| 项 | 命令(原样) | 结果 | 退出码 | 本轮由谁跑 |
|
|
368
|
+
|---|---|---|---|---|
|
|
369
|
+
| 守卫断言数 | `python -X utf8 -m md_cg.test_n225_nonobject_load` | `非对象形态索引/日志守卫(N225):65 通过,0 失败` | `0` | **本节实跑**(2026-09-29) |
|
|
370
|
+
| 每处变异红项数 | `python -X utf8 -m md_cg.test_n225_nonobject_load --branch-baseline` | 未变异基线 `失败=0`;八处红项 **25**(A20+D5) / **16**(B16) / **14**(C9+D5) / **6**(E6) / **2**(G2) / **5**(F5) / **2**(G2) / **2**(H2),逐处标注「(恰好命中预期)」;`定点变异自证:PASS` | `0` | **本节实跑** |
|
|
371
|
+
| 锚点漂移 fail-closed | 进程内把变异表换成锚点不存在的条目后调 `_branch_baseline()`(**内存改组表,不落盘、不改文件**) | `ANCHOR-MISS 假锚点探针 —— 变异锚点漂移(实现改了却没同步本表;基线源=mdcg._load_index)` | `2` | **本节实跑**(探针) |
|
|
372
|
+
| 删断言判别力 | 同上,把 E 组换成只 `begin("E")` 的空组 | `关掉「载荷坏型闸(_apply_log 跳过 e 非 dict 且非 None + 记账)」→ 红项=0,命中组={} 期望={'E': 6} **红基线失效(判别力面不符)**`、`定点变异自证:FAIL` | `1` | **本节实跑**(探针) |
|
|
373
|
+
| python 全量 | `python -X utf8 scripts/run_tests.py` | `===== SUMMARY 257/257 通过,5 跳过(依赖缺失/平台不符) =====` | `0` | **本节实跑** |
|
|
374
|
+
| 容器栈一 | `docker run --rm -v <repo>:/work -w /work -e CARGO_TARGET_DIR=/tmp/target rust:bookworm bash scripts/linux_verify.sh full` | 采集尾行:`[PASS] restart 后 serve 存活`、`结果: 21 pass / 0 fail`、`[PASS] smoke_test (linux)`、`=== 汇总: 38 pass / 0 fail ===`(21 ⊆ 38 的计数关系见 §4.2) | `0` | 本工作流采集;**本节未复跑** |
|
|
375
|
+
| 容器栈二 | `docker run --rm -v <repo>:/work -w /work node:22-bookworm bash -c "npm install --include=dev && npm run build && node --import tsx --test test/*.test.ts"` | 采集尾行:`# pass 71`、`# fail 0`、`# cancelled 0`、`# skipped 3`、`# todo 0`、`# duration_ms 5364.33548` | `0` | 本工作流采集;**本节未复跑** |
|
|
376
|
+
|
|
377
|
+
- 断言构成(读码 + 实跑一致):A 装载面 **20** · B compact 面 **16** · C 分片日志面 **9** · D stdio 端到端 **5** · E 载荷坏型收口 **6** · F 排序键健壮化 **5** · G tombstone 语义 **2** · H `read_jsonl` 通用面 **2** ⇒ **65**。
|
|
378
|
+
- 每处红项数**恰好**等于所命中组的断言数(脚本判据 `hit == expect` 且 `got_fail == sum(expect.values())`;多一项少一项即报「红基线失效」)。八处变异中 G 组出现两次、D 组随装载面/日志面两处变异一并红——都是刻意的(口径「过窄」与「过宽」必须两侧都转红;D 组的两个病理本就是「非对象快照」与「非对象行」)。
|
|
379
|
+
- **与 v1.0 §4.1 数字的关系**:`56 通过` 与被删的 E 组 5 条已不存在;变异红项表中 **C 组由 15 改 14**(C 组 10→9),另新增 6/2/5/2 四处。v1.0 §4.1 表内那两行是本轮之前的实跑记录,本节数字**取代**其口径。
|
|
380
|
+
- **为什么容器两栈本节未复跑**(如实声明):本机 `docker --version` 实跑为 `Docker version 28.4.0, build d8eb465`(Docker 可用),但**栈二的命令在仓库挂载点内执行 `npm install` 与 `npm run build`(写 `node_modules/`、`lib/`)**,栈一的 `linux_verify.sh full` 同样在挂载点内跑构建与 smoke——与本次「只改本报告文件、不得改源码/工作区」硬边界冲突;两者均未逐条评估其写面。故两栈**只记录本工作流采集的退出码,不写成本节实测**。
|
|
381
|
+
|
|
382
|
+
### 8.5 独立复核的判定与它列出的 uncovered
|
|
383
|
+
|
|
384
|
+
- **判定:`ACCEPT`**(本工作流独立复核环节)。**依据**:实体面两条残余(§8.2 腿①②)与守卫判别力缺口(§5.3 第 8 行)**均已由该环节独立复现为「已收口」**——即残余①②在改前树上真崩、在现行树上不崩且合法记录不丢;判别力面则用「整组换空 ⇒ 转红」当场证伪了上一轮的零判别力状态(本节 §8.4 第 4 行用自己的探针独立复现同一条:`rc=1` + 「红基线失效」)。
|
|
385
|
+
- **它列出的 uncovered:1 项(自标 `BLINDSPOT`)**——**报告「补强(v1.1)」段的数字核对无法执行**。它给出的证据:该报告全文 40180 字节 / 263 行,`补强` / `v1.1` / `残余` 命中数均为 0,`git diff HEAD -- docs/` 为空输出 ⇒ 被比对的该段**在复核当时不存在**。**成因是工序顺序**:工作流脚本里「报告修订员」的位次晚于「独立复核员」,追加该段的阶段排在复核之后。它同时**备好了机械基准**:该段若写「守卫断言数=65」「红项 25/16/14/6/2/5/2/2」,与其当时的实跑一致;任一处不符即报告失真。
|
|
386
|
+
- **本节即该段**:上述两处数字与那份基准**逐项一致**(§8.4 前两行为本节实跑所得,非照抄采集)。⇒ 该项 BLINDSPOT 的**内容**已由本节消除,但**「由复核方独立比对」这一步仍未发生**(该环节已结束),本节不代称其已完成。
|
|
387
|
+
- 该环节另自报的边界核验(照录):复核期间**未写工作区**(其开工与收尾的 `git status --porcelain -uall` 逐字一致);未 `git add`/`commit`/`push`;实验体全部 `tempfile.mkdtemp` 于系统临时目录并 `rmtree`(自测「临时根已清理:True」);`D:/program/AEIS` 目录 mtime 早于该会话、未被触碰。它另声明两点,与本节读码一致:守卫内**已无源码文本级断言**(见 §8.3.4 的 grep);假锚点 → `rc=2` + `ANCHOR-MISS`。
|
|
388
|
+
|
|
389
|
+
### 8.6 本轮**明确不修**的留存项(v1.0 §5.3 照留,本轮一字未动)
|
|
390
|
+
|
|
391
|
+
| # | 留存项 | 位置(工作区行号,本节读码) | 本轮状态与依据 |
|
|
392
|
+
|---|---|---|---|
|
|
393
|
+
| 1 | 记账耐久性 / 落盘账本 | `md_cg/fsutil.py:286-296`(模块级累计 + 样本,**进程内**)、`md_cg/mdcg.py:1312`(compact 后无条件 `ShardedLog.clear` 删掉被污染分片) | 未修。本节实跑全树 grep(排除 `.git`/`node_modules`/`target`/`__pycache__`)`NONOBJECT_ROW_SKIPS\|note_bad_payload_rows\|note_bad_sortkey_rows\|bad_payload_row_stats\|bad_sortkey_row_stats\|nonobject_row_stats`,命中文件仅 `md_cg/fsutil.py`、`md_cg/mdcg.py`(调用方)、`md_cg/test_n225_nonobject_load.py` 与本报告自身 ⇒ **仍无第三种消费方**;无落盘账本,重启归零 |
|
|
394
|
+
| 2 | `read_jsonl` 非法 JSON 行静默 | `md_cg/fsutil.py:223`(`except ValueError:` 后 `continue`) | 未修。「合法 JSON 但非对象」的行现在可见(§8.3),**截断/粘连等非法 JSON 行仍在 `read_jsonl` 内被吞** ⇒ `read_all` 看不见、不计数、不告警 |
|
|
395
|
+
| 3 | 同族十余处 `read_jsonl` 消费点 | v1.0 §5.3 末两行所列(`md_cg/mdcg.py`、`conformance.py`、`consolidate.py`、`mdcos.py`、`mreview/candidates.py` 等) | 未修。本轮只改 `fsutil.read_all` 与 `md_cg.MdCG._apply_log` 两条上游,其余消费点口径未动(本节未逐条复测) |
|
|
396
|
+
| 4 | 与 **N230** 的互动(`_dirty` 重放遮蔽) | `md_cg/mdcg.py:1170`(`_maybe_reload_index`) | 未修。实跑 `git diff -U0 -- md_cg/mdcg.py` 的 hunk 头(§8.3.3)**不含该函数** ⇒ 本轮未触碰;N230 指路见 §5.3 |
|
|
397
|
+
| 5 | 回退扫描无「已判定损坏」标记 | `md_cg/mdcg.py:1073`(`idx = {"schema": SCHEMA, "nodes": self._scan_nodes(), "buckets": {}}`) | 未修。快照仍为非对象期间,每个新实例构造都要全扫一次,直到某写者 compact 重写好快照 |
|
|
398
|
+
| 6 | `scan_fallback` 空扫清池面 | `md_cg/mdcg.py:1293`(降级分支以 `_scan_nodes()` 为基底)、`1312`(无条件 `ShardedLog.clear`,且降级分支不做「扫描数 vs 盘面数」对账) | 未修。该分支本身未动;触发面因本轮把「载荷坏型」挡在重放上游而**未因此而变宽**(载荷坏型不再使构造/compact 抛),但这条 low 风险的机制仍在 |
|
|
399
|
+
| 7 | 快照 `nodes` 内含非对象条目 | `md_cg/mdcg.py:1073`(顶层闸只查深度 1)、`1257-1260`(`_count_buckets` 仍裸调 `e.get("bucket")`,**未加闸**) | 未修。`_apply_log` 的 docstring 明写「`_count_buckets` 不加闸,防在上游」⇒ 日志重放面已挡住坏载荷,但**快照自带**的非对象条目仍无闸(触发条件=指纹对齐,见 §5.3 第 3 行) |
|
|
400
|
+
|
|
401
|
+
### 8.7 本节的动作留痕与纪律声明
|
|
402
|
+
|
|
403
|
+
| 动作 | 命令 / 手段 | 性质 |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| L1 直跑:守卫正向 | `python -X utf8 -m md_cg.test_n225_nonobject_load` | 只读(临时目录合成库;不改仓库/外部状态)— 低频·可逆 |
|
|
406
|
+
| L1 直跑:变异自证 | `python -X utf8 -m md_cg.test_n225_nonobject_load --branch-baseline` | 同上 |
|
|
407
|
+
| L1 直跑:python 全量 | `python -X utf8 scripts/run_tests.py` | 同上 |
|
|
408
|
+
| 只读探针(两条) | 经 stdin 传脚本、进程内改组表(假锚点/E 组换空)后调 `_branch_baseline()` | 只读(纯内存 + 临时库),**未改任何文件** |
|
|
409
|
+
| 只读核对 | `git status --porcelain -uall`、`git diff --numstat`、`git diff -U0 -- md_cg/mdcg.py`、`git show HEAD:md_cg/fsutil.py`、`git show HEAD:md_cg/mdcg.py`、`grep` / `sed` 若干、`docker --version` | 只读 |
|
|
410
|
+
| 落盘 | 本文件(**追加本节**,v1.0 正文未动) | 唯一写操作 |
|
|
411
|
+
|
|
412
|
+
- **兜底声明(工作纪律第 7 条)**:本会话工具面**无 mdcg 工具**;进程内直调兜底亦**未执行**——实跑 `echo $MDCG_ROOT` 为空、`$HOME` 下未探测到记忆库根,无法在不承担写风险的前提下定位记忆库。故 `cg(op=route, intent=…)` **未执行(声明式跳过,非静默跳过)**。
|
|
413
|
+
- 按工作纪律第 4 条: 根因纪律——先取证现场不猜测; 根因找到并验证后归档认知图并补不适用条件。
|
|
414
|
+
- 按工作纪律第 5 条: 验证纪律——入库前必须过回放/断言/回归验证, 未验证不固化。
|
|
415
|
+
- 按工作纪律第 8 条: 中文思考——中文书写; 复杂(新奇/挑战/曾受批评/高信息差)→「我们需要」交流确认互补盲区; 简单→「让我」快速执行不空耗; 每段推理开篇用中文短语锚定语言。
|
|
416
|
+
- 按工作纪律第 15 条: 命令执行统一走python——argv列表+显式UTF-8+PYTHONUTF8=1, 不经Windows shell, 规避GBK解码异常。
|
|
417
|
+
- 按工作纪律第 17 条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道, 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。
|
|
418
|
+
- 按工作纪律第 18 条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。(本节为已明确路径的单文件操作,属该条「不适用」面。)
|