@furongjun1999/dsh-memory 0.7.2 → 0.7.4

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.
Files changed (129) hide show
  1. package/README.md +57 -47
  2. package/codebuddy/CODEBUDDY.md +6 -6
  3. package/docs/README.md +148 -144
  4. package/docs/discipline/harnesses.yaml +16 -8
  5. package/docs/discipline/templates/zcode-user.md.tmpl +49 -0
  6. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_/345/205/255/345/256/266/350/256/260/345/277/206/347/263/273/347/273/237/345/220/214/345/260/272/345/256/236/346/265/213_v3.1.md +340 -0
  7. 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 +2 -2
  8. package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +1 -1
  9. package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.1.md +1 -1
  10. 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 +1 -1
  11. 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 +2 -2
  12. package/docs/eval/issue50_/345/205/203/346/225/260/346/215/256/351/200/217/344/274/240/344/270/216/345/205/234/345/272/225_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +5 -5
  13. package/docs/eval/issue50_/345/215/212/351/207/215/345/244/215/345/276/205/345/256/232/345/244/215/346/240/270_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +9 -9
  14. package/docs/eval/issue50_/345/276/205/345/256/232/345/244/215/346/240/270/345/205/245/351/230/237_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +4 -4
  15. package/docs/eval/issue50_/351/207/215/350/246/201/345/272/246/345/220/214/346/272/220/344/270/216/344/277/235/346/212/244/350/257/255/344/271/211_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +1 -1
  16. package/docs/eval/issue51_/344/270/200/351/224/256/345/256/211/350/243/205/345/244/261/350/264/245_/345/275/222/345/261/236/345/210/244/345/256/232_v1.0.md +1 -1
  17. package/docs/eval/issue52_/346/235/241/344/273/266/345/205/210/350/241/214/344/270/216/346/210/252/346/226/255/345/217/257/350/247/202/346/265/213_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +3 -3
  18. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/242/345/217/230/346/233/264/345/215/225/344/270/216/345/233/236/346/273/232/345/216/237/350/257/255_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +3 -3
  19. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/243/345/207/206/345/205/245/350/257/273/346/225/260_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +7 -7
  20. 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 +2 -2
  21. 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 +1 -1
  22. 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 +7 -7
  23. 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 +10 -10
  24. 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 +4 -0
  25. 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 +1 -1
  26. package/docs/eval//345/217/221/345/270/20311_/345/244/226/346/212/245/345/205/255/350/277/236/344/277/256/344/270/216/344/270/226/347/225/214/346/250/241/345/236/213/345/272/225/345/272/247_v1.0.md +91 -0
  27. package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +108 -0
  28. 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 +2 -2
  29. package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/270/200/346/211/271_v1.0.md +195 -0
  30. package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/272/214/346/211/271B6_/346/234/254/346/234/272/350/267/257/345/276/204/347/233/270/345/257/271/345/214/226_v1.0.md +78 -0
  31. package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +1 -1
  32. package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +3 -3
  33. package/docs/eval//347/254/2543/345/261/202stg/347/273/223/346/236/204/347/264/242/345/274/225_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +1 -1
  34. 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 +1 -1
  35. 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_v26.md +196 -0
  36. 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_v27.md +259 -0
  37. 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_v28.md +217 -0
  38. 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_v29.md +270 -0
  39. 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_v30.md +231 -0
  40. 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_v31.md +281 -0
  41. package/docs/eval//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_P1_/346/212/275/345/217/226/345/231/250v4_/344/270/211/347/216/207/350/257/273/346/225/260_v1.0.md +231 -0
  42. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +1 -1
  43. 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
  44. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +1 -1
  45. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +17 -17
  46. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +1 -1
  47. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +2 -2
  48. package/docs/mdcg//344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/344/275/277/347/224/250/344/270/216/350/277/220/347/273/264_v1.0.md +305 -0
  49. 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 +45 -42
  50. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +29 -8
  51. 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 +71 -1
  52. 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 +1 -1
  53. package/docs/plans//345/244/232/344/270/273/344/275/223/344/270/226/347/225/214/346/250/241/345/236/213_/345/257/271/351/275/220/350/257/204/344/274/260/344/270/216/350/220/275/345/234/260/350/256/276/350/256/241_v0.1.md +141 -0
  54. 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 +2 -2
  55. package/docs/plans//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/350/256/276/350/256/241_v0.1.md +107 -0
  56. 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 +6 -5
  57. package/docs//347/247/244_/350/256/260/345/277/206/347/263/273/347/273/237/350/257/204/346/265/213/350/247/204/350/214/203_v2.1.md +225 -0
  58. package/dsh/cordis-patch-profile-web.example.yml +2 -2
  59. package/lib/bridge.js +9 -0
  60. package/lib/hooks.d.ts +8 -1
  61. package/lib/hooks.js +134 -50
  62. package/lib/lib/hook_audit.d.ts +55 -0
  63. package/lib/lib/hook_audit.js +208 -0
  64. package/lib/lib/mutual.js +5 -1
  65. package/lib/lib/roleplay_web.js +17 -2
  66. package/lib/lib/session_state.d.ts +56 -0
  67. package/lib/lib/session_state.js +124 -0
  68. package/lib/tools.js +12 -1
  69. package/md_cg/auditview.py +248 -0
  70. package/md_cg/bench_e2e_judge.py +4 -2
  71. package/md_cg/branches.py +29 -3
  72. package/md_cg/consistency.py +38 -13
  73. package/md_cg/datapath.py +38 -11
  74. package/md_cg/forgetting.py +15 -2
  75. package/md_cg/freshness.py +32 -11
  76. package/md_cg/ghostref.py +130 -0
  77. package/md_cg/judgment_manifest.py +7 -0
  78. package/md_cg/lifecycle.py +38 -0
  79. package/md_cg/mcp_server.py +122 -12
  80. package/md_cg/mdcg.py +4923 -4822
  81. package/md_cg/mdcos.py +109 -21
  82. package/md_cg/nodefile.py +77 -13
  83. package/md_cg/readcache.py +29 -4
  84. package/md_cg/selfreport.py +12 -3
  85. package/md_cg/sleep.py +129 -13
  86. package/md_cg/sources.py +1 -1
  87. package/md_cg/state_events.py +131 -0
  88. package/md_cg/state_slots.py +221 -0
  89. package/md_cg/stg.py +68 -3
  90. package/md_cg/sustain.py +1436 -1436
  91. package/md_cg/test_auditview.py +277 -0
  92. package/md_cg/test_autonomy_modes.py +2 -1
  93. package/md_cg/test_boot_openblas_guard.py +197 -0
  94. package/md_cg/test_ccg_form_parity.py +95 -4
  95. package/md_cg/test_cons200_scan_selection.py +4 -2
  96. package/md_cg/test_datapath_device_name.py +62 -3
  97. package/md_cg/test_generation_guard.py +5 -2
  98. package/md_cg/test_ghostref.py +182 -0
  99. package/md_cg/test_interop.py +1 -1
  100. package/md_cg/test_issue52_scan_condition_first.py +1 -1
  101. package/md_cg/test_lifecycle_retire_leak.py +408 -0
  102. package/md_cg/test_lock.py +16 -7
  103. package/md_cg/test_m3_h9_semantic_guard.py +1 -1
  104. package/md_cg/test_n230_dirty_replay.py +375 -375
  105. package/md_cg/test_n238_body_text_parity.py +156 -0
  106. package/md_cg/test_n276_probe_hoist.py +199 -0
  107. package/md_cg/test_p4_freshness.py +95 -1
  108. package/md_cg/test_policy_required_ccg.py +3 -1
  109. package/md_cg/test_read_face_semantics.py +6 -3
  110. package/md_cg/test_readcache_prodpath.py +66 -0
  111. package/md_cg/test_sleep.py +58 -1
  112. package/md_cg/test_sleep_p1.py +218 -2
  113. package/md_cg/test_state_event_op.py +514 -0
  114. package/md_cg/test_state_events.py +178 -0
  115. package/md_cg/test_state_slots.py +691 -0
  116. package/md_cg/test_time_core_lint.py +8 -1
  117. package/md_cg/test_writepipe.py +8 -5
  118. package/md_cg/tokens.py +18 -2
  119. package/md_cg/writepipe.py +76 -0
  120. package/package.json +2 -2
  121. package/skills/plugin.json +1 -1
  122. package/src/bridge.ts +9 -0
  123. package/src/hooks.ts +599 -517
  124. package/src/lib/hook_audit.ts +243 -0
  125. package/src/lib/mutual.ts +5 -1
  126. package/src/lib/roleplay_web.ts +17 -2
  127. package/src/lib/session_state.ts +127 -0
  128. package/src/tools.ts +12 -1
  129. package/zcode/AGENTS.md +6 -6
@@ -34,6 +34,14 @@
34
34
  G7 冲突挂起——主库/影子同 id 并发改动 ⇒ 该 id 进 `conflicts`、**未写进主库**、
35
35
  也未进影子分支的树;另一条(未冲突的)正常合并。
36
36
  G8 回滚——对合并提交 `revert`:主库真源面回到合并前,历史不丢(只增不减)。
37
+ G9 物化窗口内漂移(issue #60)——`ok` 与 `face_stable` 拆开后,活跃工作区
38
+ (每轮都有真源面改动)**照常迭代**:phases 含 iterate、②③④ 不再记
39
+ 「本轮未迭代」、台账落一条含 iterate 的记录。
40
+ G10 手动入口(issue #61)——`--once` 不等 tick 即落台账并打印与台账同源的
41
+ 「本轮未迭代原因」;`--once --dry-run` 只预览(零台账新增、零物化);
42
+ `--dry-run` 单独使用报错;`--status` 带 `last_cycle` 段。
43
+ G11 物化未完成(父进程持根级锁 ⇒ busy)——②③④ 记「物化未完成:…」,
44
+ 与门拦 gate_reason **可区分**(`rec["skipped"]` 为空、两处文案同源)。
37
45
 
38
46
  运行:python -X utf8 -m md_cg.test_sleep_p1
39
47
  python -X utf8 -m md_cg.test_sleep_p1 --mutate # 定点变异自证
@@ -59,6 +67,7 @@ import types
59
67
 
60
68
  from . import sleep as SL
61
69
  from . import sustain as SUS
70
+ from .fsutil import FileLock
62
71
 
63
72
  _PASS = []
64
73
  _FAIL = []
@@ -611,7 +620,203 @@ def g8_revert():
611
620
  "G8e 历史只增不减(revert 自身也进历史)")
612
621
 
613
622
 
614
- _GROUPS = (g0, g8, g1, g2, g3, g4, g5, g6, g7, g8_revert)
623
+ # ---------------------------------------------------------------- G9
624
+ class _Cap(io.StringIO):
625
+ """捕获 `_main` 的输出——它开头会调 `sys.stdout.reconfigure`,纯 StringIO
626
+ 没有这个方法(真 stdout 才有),故补一个空实现。"""
627
+
628
+ def reconfigure(self, **_kw): # noqa: D102 —— 空实现
629
+ pass
630
+
631
+
632
+ def _drift_probe(S, *, at=2, rel="knowledge/n_drift.md"):
633
+ """物化窗口内漂移模拟(补丁就地装上):第 `at` 次调用 `source_face_hashes` 时
634
+ 多返回一条新增路径(其余调用照真源);返回**还原函数**。
635
+
636
+ 补丁打在**函数实际读取的命名空间**上(`materialize.__globals__`)而不是模块
637
+ 属性:变异自证时 `SL` 是 exec 出来的模块,其函数读 exec 命名空间,而模块对象
638
+ 的 `__dict__` 只是它的副本——打在模块属性上,变异后的模块内部调用看不到补丁
639
+ (本机实测:G9 会在变异下空转)。真源码下两者是同一个 dict。
640
+ """
641
+ ns = S.materialize.__globals__
642
+ real = ns["source_face_hashes"]
643
+ seen = {"n": 0}
644
+
645
+ def _wrap(r):
646
+ seen["n"] += 1
647
+ d = dict(real(r))
648
+ if seen["n"] == at:
649
+ d[rel] = "0" * 64
650
+ return d
651
+
652
+ ns["source_face_hashes"] = _wrap
653
+
654
+ def _restore():
655
+ ns["source_face_hashes"] = real
656
+
657
+ return _restore
658
+
659
+
660
+ def g9():
661
+ print("== G9 物化窗口内漂移 ⇒ 照常迭代(#60:不再自我否决)==")
662
+ S = globals()["SL"]
663
+ gen, root, state = _fixture(contextual_hot=1, unverified=1)
664
+ from .mdcos import MdCGOS
665
+ cg = MdCGOS(root)
666
+ n0 = len(S.read_ledger())
667
+ restore = _drift_probe(S)
668
+ try:
669
+ r = S.run_cycle(cg, batch="G9B1", round_index=1, now="03:00")
670
+ finally:
671
+ restore()
672
+ ph = r["steps"][6]["phases"]
673
+ mat = ph.get("materialize") or {}
674
+ ok(mat.get("ok") is True and mat.get("face_stable") is False,
675
+ "G9a 漂移窗口内物化仍记 ok=True、face_stable=False", mat)
676
+ ok(bool(ph.get("iterate")),
677
+ "G9b **物化成功即迭代**:phases 含 iterate 读数(非 None)", sorted(ph))
678
+ ok(all(not s.get("skipped") for s in r["steps"][1:4]),
679
+ "G9c 九步 ②③④ 不再是「本轮未迭代」(裁在门拦或物化未完成,二者皆非)",
680
+ [s.get("skipped") for s in r["steps"][1:4]])
681
+ ok(len(S.read_ledger()) == n0 + 1, "G9d 本轮记账(台账 +1)",
682
+ (n0, len(S.read_ledger())))
683
+ led = S.read_ledger()[-1]
684
+ ok(led.get("batch") == "G9B1"
685
+ and "iterate" in ((led.get("steps") or [{}] * 7)[6].get("phases") or {}),
686
+ "G9e 台账落一条**含 iterate** 的记录(可回看「上次为何迭代/未迭代」)",
687
+ (led.get("batch"), sorted((((led.get("steps") or [{}] * 7)[6]
688
+ .get("phases") or {})))))
689
+
690
+
691
+ # ---------------------------------------------------------------- G10
692
+ def g10():
693
+ print("== G10 手动入口:--once / --once --dry-run / --status(#61 不等 tick)==")
694
+ S = globals()["SL"]
695
+ import time as _t
696
+ gen, root, state = _fixture(contextual_hot=1, unverified=1)
697
+ lt = _t.localtime()
698
+ cur = lt.tm_hour * 60 + lt.tm_min
699
+ a0, b0 = (cur + 120) % 1440, (cur + 180) % 1440
700
+ win = "%02d:%02d-%02d:%02d" % (a0 // 60, a0 % 60, b0 // 60, b0 % 60)
701
+ ok(S.in_window(win, None) is False,
702
+ "G10 前置:构造出的窗口确定性地不含当前时刻(%s)" % win)
703
+ # ① --once --dry-run(窗口内):零台账新增、零物化,打印「将执行…」
704
+ os.environ["MDCG_SLEEP_WINDOW"] = ""
705
+ n0 = len(S.read_ledger())
706
+ cap = _Cap()
707
+ with contextlib.redirect_stdout(cap):
708
+ rc = S._main(["--root", root, "--once", "--dry-run"])
709
+ txt = cap.getvalue()
710
+ ok(rc == 0 and len(S.read_ledger()) == n0,
711
+ "G10a --once --dry-run **零台账新增**", (rc, n0, len(S.read_ledger())))
712
+ ok("将执行物化→迭代→对账→合并" in txt,
713
+ "G10b 预览打印「将执行物化→迭代→对账→合并」",
714
+ [l for l in txt.splitlines() if l.startswith("预览")])
715
+ ok(not os.path.isdir(os.path.join(state, "sleep", "shadow"))
716
+ and not os.path.isdir(os.path.join(state, "sleep", "lib.git")),
717
+ "G10c dry-run **不物化**(影子与版本库都没建出来)")
718
+ # ② --dry-run 单独使用 → 报错
719
+ cap2, err2 = _Cap(), io.StringIO()
720
+ with contextlib.redirect_stdout(cap2), contextlib.redirect_stderr(err2):
721
+ rc2 = S._main(["--root", root, "--dry-run"])
722
+ ok(rc2 == 2 and "--dry-run" in err2.getvalue(),
723
+ "G10d --dry-run 单独使用给出报错(返回 2,不执行任何动作)",
724
+ (rc2, err2.getvalue().strip()))
725
+ # ③ --once(窗口外,确定性构造):落台账 + 打印与台账同源的原因
726
+ os.environ["MDCG_SLEEP_WINDOW"] = win
727
+ n1 = len(S.read_ledger())
728
+ cap3 = _Cap()
729
+ with contextlib.redirect_stdout(cap3):
730
+ rc3 = S._main(["--root", root, "--once"])
731
+ txt3 = cap3.getvalue()
732
+ led = S.read_ledger()
733
+ ok(rc3 == 0 and len(led) == n1 + 1 and len(led) > n0,
734
+ "G10e --once 不等 tick 即落一条台账记录", (rc3, n1, len(led)))
735
+ rec = led[-1]
736
+ ok("窗口外" in (rec.get("skipped") or ""),
737
+ "G10e2 台账记门拦原因(窗口外)", rec.get("skipped"))
738
+ ok("本轮未迭代原因:" in txt3 and (rec.get("skipped") or "") in txt3,
739
+ "G10e3 --once 显式打印「本轮未迭代原因」,文案与台账同源",
740
+ [l for l in txt3.splitlines() if l.startswith("本轮未迭代原因")])
741
+ ok(all(s.get("skipped") == rec.get("skipped") for s in rec["steps"][1:4]),
742
+ "G10e4 台账 ②③④ 的 skip 文案 == 同一门拦原因(不静默)",
743
+ [s.get("skipped") for s in rec["steps"][1:4]])
744
+ # ④ --status:last_cycle = 最近一条台账记录
745
+ def _status():
746
+ capx = _Cap()
747
+ with contextlib.redirect_stdout(capx):
748
+ rcx = S._main(["--root", root, "--status"])
749
+ return rcx, json.loads(capx.getvalue())
750
+
751
+ rcx, doc = _status()
752
+ lc = doc.get("last_cycle") or {}
753
+ ok(rcx == 0 and lc.get("batch") == rec.get("batch")
754
+ and lc.get("round") == rec.get("round")
755
+ and lc.get("candidates") == rec.get("candidates")
756
+ and lc.get("skipped") == rec.get("skipped"),
757
+ "G10f --status 的 last_cycle = 最近一条台账记录(t/batch/round/"
758
+ "candidates/skipped)", lc)
759
+ ok(lc.get("not_iterated_reason") == rec.get("skipped")
760
+ and "窗口外" in (lc.get("not_iterated_reason") or ""),
761
+ "G10f2 last_cycle 带「上次为何没迭代」的一致原因",
762
+ lc.get("not_iterated_reason"))
763
+ ok(lc.get("steps_skip") == {s.get("step"): s.get("skipped")
764
+ for s in rec["steps"][1:4]},
765
+ "G10f3 last_cycle 带 ②③④ 的 skip 文案", lc.get("steps_skip"))
766
+ # ⑤ --once(窗口内):真跑一轮(物化→迭代→对账→合并)后再看 --status
767
+ os.environ["MDCG_SLEEP_WINDOW"] = ""
768
+ cap4 = _Cap()
769
+ with contextlib.redirect_stdout(cap4):
770
+ rc4 = S._main(["--root", root, "--once"])
771
+ rec4 = S.read_ledger()[-1]
772
+ ok(rc4 == 0 and rec4.get("skipped") is None
773
+ and (rec4.get("candidates") or 0) > 0,
774
+ "G10g 窗口内 --once 真迭代(非只记账)",
775
+ {k: rec4.get(k) for k in ("skipped", "candidates", "merged")})
776
+ rcx2, doc2 = _status()
777
+ lc2 = doc2.get("last_cycle") or {}
778
+ mz = lc2.get("materialize") or {}
779
+ ok(isinstance(mz, dict) and all(k in mz for k in ("ok", "busy", "face_stable"))
780
+ and mz.get("ok") is True and mz.get("busy") is False
781
+ and mz.get("face_stable") is True,
782
+ "G10h last_cycle 带 phases.materialize 摘要(ok/busy/face_stable)", mz)
783
+ ok(lc2.get("not_iterated_reason") is None,
784
+ "G10h2 迭代过的一轮不报「未迭代原因」", lc2.get("not_iterated_reason"))
785
+ os.environ.pop("MDCG_SLEEP_WINDOW", None)
786
+
787
+
788
+ # ---------------------------------------------------------------- G11
789
+ def g11():
790
+ print("== G11 物化未完成(根级锁被占)⇒ ②③④ 记**可区分**的 skip 文案 ==")
791
+ S = globals()["SL"]
792
+ gen, root, state = _fixture(contextual_hot=1, unverified=1)
793
+ from .mdcos import MdCGOS
794
+ cg = MdCGOS(root)
795
+ lp = S.lock_path()
796
+ with FileLock(lp, timeout=5.0, strict=False) as lk:
797
+ ok(lk.acquired is True, "G11 前置:父进程已持根级锁", lp)
798
+ r = S.run_cycle(cg, batch="G11B1", round_index=1, now="03:00",
799
+ timeout=0.3)
800
+ ph = r["steps"][6]["phases"].get("materialize") or {}
801
+ ok(ph.get("ok") is False and ph.get("busy") is True,
802
+ "G11a 取不到根级锁 ⇒ 物化 ok=False、busy=True(早退路径语义不变)", ph)
803
+ texts = [s.get("skipped") for s in r["steps"][1:4]]
804
+ ok(all(t and t.startswith("物化未完成:") for t in texts),
805
+ "G11b ②③④ 记「物化未完成:…」——与门拦 gate_reason **可区分**", texts)
806
+ ok(r.get("skipped") is None,
807
+ "G11c 四道门全过 ⇒ rec['skipped'] 为空:两类未迭代原因不混同",
808
+ r.get("skipped"))
809
+ ok(S.cycle_not_iterated_reason(r) == texts[0],
810
+ "G11d 「本轮未迭代原因」== 台账 ②③④ 的同一文案(CLI 与台账同源)",
811
+ S.cycle_not_iterated_reason(r))
812
+ led = S.read_ledger()[-1]
813
+ ok(led.get("batch") == "G11B1"
814
+ and all((s.get("skipped") or "").startswith("物化未完成:")
815
+ for s in led["steps"][1:4]),
816
+ "G11e 物化未完成的一轮同样落台账(可回看)", led.get("batch"))
817
+
818
+
819
+ _GROUPS = (g0, g8, g1, g2, g3, g4, g5, g6, g7, g8_revert, g9, g10, g11)
615
820
 
616
821
 
617
822
  def _run_groups() -> int:
@@ -639,8 +844,14 @@ _MUTATIONS = (
639
844
  ("窗口跨午夜判据写反", "md_cg/sleep.py",
640
845
  " return t >= start or t < end",
641
846
  " return t >= start and t < end"),
847
+ # 锚点同步(2026-10-06):本条此前锚在裁定⑰之前的旧 parse_window 形态上,实现
848
+ # 改了而表未同步 ⇒ `--mutate` 恒以 ANCHOR-MISS 退出 2(**先存**缺陷,非本批引入;
849
+ # 判据 scripts/run_tests.py 不跑 --mutate,故一直未被门禁兜住)。此处按当前实现
850
+ # 重新锚定**同一意图**的变异(留空/非法不再当作全时段,回落 (0,1) 窄窗)——不放宽
851
+ # 也不删除任何既有断言。实测红集 7 项 = G1f/G1h/G1h3 + 依赖空窗全时段入口的新
852
+ # 断言组 G10b/G10g/G10h(编排侧 --mutate 亲跑读数)。
642
853
  ("留空/非法窗口不再视作全时段", "md_cg/sleep.py",
643
- " s = str(spec or \"\").strip()\n if not s or \"-\" not in s:\n return None",
854
+ " s = str(spec or \"\").strip()\n if not s:\n return None # 留空 = 全时段(唯一显式入口)",
644
855
  " s = str(spec or \"\").strip()\n if not s or \"-\" not in s:\n return (0, 1)"),
645
856
  ("密文项不再被闸拦下(会随分支入册)", "md_cg/sleep.py",
646
857
  ' "reason": ("影子侧为密文节点(无密钥不重放、"\n'
@@ -676,6 +887,11 @@ _MUTATIONS = (
676
887
  " pass # 睡眠周期失败不中断常驻\n"
677
888
  " next_sleep = now + self.sleep_interval\n",
678
889
  ""),
890
+ # issue #60:物化 ok 语义回退成旧「真源面未变才算成功」——活跃工作区(每轮
891
+ # 都有真源面改动)将永不自迭代;预期红集 = 断言组 G9(G9b/G9c/G9e)。
892
+ ("物化 ok 语义回退(漂移即不迭代)", "md_cg/sleep.py",
893
+ ' if m.get("ok"):\n # **物化成功即迭代**(issue #60)',
894
+ ' if m.get("ok") and m.get("face_stable"):\n # **物化成功即迭代**(issue #60)'),
679
895
  ("两入口不再共用 sleep 读取器(写死 auto)", "md_cg/mcp_server.py",
680
896
  " sleep_interval=_sleep.sleep_interval(),\n"
681
897
  " auto_sleep=_sleep.sleep_enabled(),\n"