@furongjun1999/dsh-memory 0.4.7 → 0.4.9

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 (325) hide show
  1. package/README.md +130 -47
  2. package/codebuddy/CODEBUDDY.md +21 -10
  3. package/codebuddy/README.md +13 -1
  4. package/codebuddy/mcp.json +9 -0
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -0
  6. package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -0
  7. package/docs/README.md +111 -0
  8. package/docs/discipline/harnesses.yaml +226 -152
  9. package/docs/discipline/templates/full.md.tmpl +61 -58
  10. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
  11. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
  12. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
  13. package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
  14. package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
  15. package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
  16. package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
  17. package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
  18. package/docs/experiments/m4-role-probe/census.py +86 -0
  19. package/docs/experiments/m4-role-probe/probe.py +89 -0
  20. package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
  21. package/docs/experiments/mapped_confidence/post_check.py +75 -0
  22. package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
  23. package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
  24. package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
  25. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -0
  26. 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 +102 -0
  27. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
  28. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
  29. 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 +503 -0
  30. package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
  31. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
  32. package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
  33. package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
  34. package/docs/mdcg//344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/345/245/221/347/272/246_v0.1.md +82 -0
  35. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -0
  36. 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 +263 -0
  37. package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
  38. package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -0
  39. package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
  40. package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
  41. 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 +172 -0
  42. package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
  43. package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
  44. package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
  45. 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 +4 -4
  46. package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
  47. package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
  48. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
  49. package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
  50. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
  51. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -0
  52. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -0
  53. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
  54. package/docs/{ → theory/}/347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +6 -6
  55. package/docs/{ → theory/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
  56. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -0
  57. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
  58. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
  59. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
  60. 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 +434 -405
  61. package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
  62. package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
  63. package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
  64. package/dsh/README.md +34 -1
  65. package/dsh/cordis.yml.example +13 -7
  66. package/dsh/hive-mcp-probe.mjs +94 -0
  67. package/dsh/hive-mcp.example.yml +62 -0
  68. package/dsh/update-lingshu.bat +11 -0
  69. package/dsh/update-lingshu.ps1 +337 -0
  70. package/lib/hooks.d.ts +8 -0
  71. package/lib/hooks.js +27 -24
  72. package/lib/index.d.ts +4 -2
  73. package/lib/index.js +29 -6
  74. package/lib/lib/datapath.d.ts +76 -1
  75. package/lib/lib/datapath.js +199 -13
  76. package/lib/lib/mdcg_client.d.ts +6 -3
  77. package/lib/lib/mdcg_client.js +6 -5
  78. package/lib/lib/mutual.js +4 -4
  79. package/lib/lib/prompt_safety.d.ts +48 -0
  80. package/lib/lib/prompt_safety.js +62 -0
  81. package/lib/lib/token_store.js +4 -5
  82. package/md_cg/audit.py +91 -2
  83. package/md_cg/autonomy.py +86 -15
  84. package/md_cg/backfill.py +36 -1
  85. package/md_cg/backfill_bigdomain.py +34 -0
  86. package/md_cg/bench6_arms.py +28 -1
  87. package/md_cg/bench6_common.py +10 -1
  88. package/md_cg/bench6_competitors.py +6 -1
  89. package/md_cg/bench_axis_domain.py +9 -1
  90. package/md_cg/bench_blind_comp.py +7 -1
  91. package/md_cg/bench_en_atoms_public.py +9 -0
  92. package/md_cg/bench_governance.py +348 -0
  93. package/md_cg/bench_lme_zh.py +16 -1
  94. package/md_cg/bench_locomo.py +2 -1
  95. package/md_cg/bench_locomo_zh.py +16 -1
  96. package/md_cg/bench_locomo_zh_public.py +4 -1
  97. package/md_cg/bench_longmem.py +2 -1
  98. package/md_cg/bench_membench.py +37 -6
  99. package/md_cg/bench_p0.py +4 -1
  100. package/md_cg/bench_progressive.py +61 -10
  101. package/md_cg/bench_role_views.py +238 -0
  102. package/md_cg/bench_task_ab.py +8 -1
  103. package/md_cg/bench_task_ab_llm.py +13 -1
  104. package/md_cg/bench_unified_en.py +6 -1
  105. package/md_cg/bench_zh_mad.py +20 -1
  106. package/md_cg/blindspot_tickets.py +123 -0
  107. package/md_cg/branches.py +286 -0
  108. package/md_cg/build_postings.py +73 -0
  109. package/md_cg/ccgc.py +949 -0
  110. package/md_cg/census.py +5 -1
  111. package/md_cg/chain.py +24 -3
  112. package/md_cg/codeindex.py +134 -17
  113. package/md_cg/coldverify.py +265 -0
  114. package/md_cg/comment_gate.py +338 -0
  115. package/md_cg/cond_compose.py +190 -0
  116. package/md_cg/cond_facts.py +155 -0
  117. package/md_cg/cond_template.json +107 -0
  118. package/md_cg/condition_anchor.py +143 -0
  119. package/md_cg/conformance.py +727 -0
  120. package/md_cg/consistency.py +25 -2
  121. package/md_cg/consolidate.py +53 -2
  122. package/md_cg/corpus.py +5 -1
  123. package/md_cg/crosscheck.py +42 -2
  124. package/md_cg/crypto.py +35 -1
  125. package/md_cg/d_meta.py +310 -0
  126. package/md_cg/datapath.py +201 -26
  127. package/md_cg/docindex.py +122 -1
  128. package/md_cg/eval_common.py +35 -6
  129. package/md_cg/evidence.py +27 -1
  130. package/md_cg/evolution.py +21 -1
  131. package/md_cg/export.py +11 -1
  132. package/md_cg/forgetting.py +34 -4
  133. package/md_cg/fsutil.py +81 -2
  134. package/md_cg/hotcache.py +214 -0
  135. package/md_cg/hyperedge.py +251 -0
  136. package/md_cg/identity.py +20 -3
  137. package/md_cg/insight.py +19 -3
  138. package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
  139. package/md_cg/lexicon/build_standard_en.py +171 -168
  140. package/md_cg/lexicon/expand_en_zh.py +6 -0
  141. package/md_cg/lifecycle.py +273 -0
  142. package/md_cg/linkref.py +281 -0
  143. package/md_cg/links.py +29 -1
  144. package/md_cg/mcp_server.py +894 -168
  145. package/md_cg/md_whitebox.py +53 -1
  146. package/md_cg/mdcg.py +1205 -41
  147. package/md_cg/mdcos.py +1060 -76
  148. package/md_cg/metacognition.py +39 -4
  149. package/md_cg/migrate.py +4 -0
  150. package/md_cg/migrate_aeis.py +221 -213
  151. package/md_cg/migrate_roleplay.py +8 -0
  152. package/md_cg/migrate_wisdom_graph.py +14 -1
  153. package/md_cg/mreview/__init__.py +25 -0
  154. package/md_cg/mreview/__main__.py +110 -0
  155. package/md_cg/mreview/bundle.py +178 -0
  156. package/md_cg/mreview/candidates.py +262 -0
  157. package/md_cg/mreview/govern.py +694 -0
  158. package/md_cg/mreview/locate.py +939 -0
  159. package/md_cg/mreview/pipeline.py +729 -0
  160. package/md_cg/mreview/rules/duplication.json +21 -0
  161. package/md_cg/mreview/rules/field_coverage.json +54 -0
  162. package/md_cg/mreview/rules/source_license.json +21 -0
  163. package/md_cg/mreview/rules/template_flow.json +21 -0
  164. package/md_cg/mreview/ruleset.py +253 -0
  165. package/md_cg/nodefile.py +269 -1
  166. package/md_cg/pooling.py +23 -1
  167. package/md_cg/postings.py +298 -0
  168. package/md_cg/predict.py +113 -15
  169. package/md_cg/progressive.py +3 -0
  170. package/md_cg/protect.py +18 -5
  171. package/md_cg/protocol.py +372 -0
  172. package/md_cg/provenance.py +262 -0
  173. package/md_cg/reach.py +453 -0
  174. package/md_cg/refindex.py +88 -4
  175. package/md_cg/refine.py +20 -1
  176. package/md_cg/roleviews.py +89 -0
  177. package/md_cg/routing.py +76 -0
  178. package/md_cg/scrub.py +68 -3
  179. package/md_cg/security.py +26 -1
  180. package/md_cg/self_state.py +65 -2
  181. package/md_cg/selfreport.py +151 -0
  182. package/md_cg/semantic/canonical.py +5 -0
  183. package/md_cg/semantic/en_normalizer.py +364 -295
  184. package/md_cg/semantic/zh_en_atoms.py +139 -136
  185. package/md_cg/signer.py +41 -1
  186. package/md_cg/sources.py +583 -547
  187. package/md_cg/statushdr.py +179 -0
  188. package/md_cg/stg.py +59 -18
  189. package/md_cg/subgraph.py +23 -0
  190. package/md_cg/sustain.py +74 -1
  191. package/md_cg/tasks.py +471 -0
  192. package/md_cg/test_action_derive.py +203 -0
  193. package/md_cg/test_audit_rotate.py +270 -0
  194. package/md_cg/test_autonomy.py +26 -0
  195. package/md_cg/test_bench_governance.py +102 -0
  196. package/md_cg/test_blindspot_tickets.py +166 -0
  197. package/md_cg/test_branches.py +249 -0
  198. package/md_cg/test_ccg_perturb.py +1 -1
  199. package/md_cg/test_ccgc.py +433 -0
  200. package/md_cg/test_codeindex.py +338 -0
  201. package/md_cg/test_comment_gate.py +187 -0
  202. package/md_cg/test_cond_compose_anchors.py +76 -0
  203. package/md_cg/test_condition_anchor.py +82 -0
  204. package/md_cg/test_conformance.py +343 -0
  205. package/md_cg/test_d_meta.py +412 -0
  206. package/md_cg/test_datapath_root.py +188 -0
  207. package/md_cg/test_en_pipeline.py +24 -0
  208. package/md_cg/test_gain_gate.py +47 -1
  209. package/md_cg/test_health_scale.py +173 -0
  210. package/md_cg/test_hot_cold.py +187 -0
  211. package/md_cg/test_hyperedge.py +245 -0
  212. package/md_cg/test_identity_attribution.py +6 -0
  213. package/md_cg/test_index_durability.py +224 -0
  214. package/md_cg/test_lifecycle.py +309 -0
  215. package/md_cg/test_linkref.py +306 -0
  216. package/md_cg/test_md_access_parity.py +15 -3
  217. package/md_cg/test_mr_m1.py +769 -0
  218. package/md_cg/test_mr_m2.py +587 -0
  219. package/md_cg/test_mr_m3.py +710 -0
  220. package/md_cg/test_mr_m4.py +485 -0
  221. package/md_cg/test_p1.py +1 -1
  222. package/md_cg/test_p11_consistency.py +1 -1
  223. package/md_cg/test_p12_metacognition.py +2 -2
  224. package/md_cg/test_p16_self_state.py +1 -1
  225. package/md_cg/test_p26_refindex.py +50 -21
  226. package/md_cg/test_p27_docindex.py +258 -4
  227. package/md_cg/test_p28_refcheck.py +1 -1
  228. package/md_cg/test_p29_session_ingest_export.py +1 -1
  229. package/md_cg/test_p2_mcp.py +7 -1
  230. package/md_cg/test_p31_insight.py +24 -0
  231. package/md_cg/test_p38_contextualize.py +1 -1
  232. package/md_cg/test_p39_vision_evidence.py +1 -1
  233. package/md_cg/test_p40_refine_worklist.py +1 -1
  234. package/md_cg/test_p41_evolve_patrol.py +1 -1
  235. package/md_cg/test_p42_provenance.py +1 -1
  236. package/md_cg/test_p43_pooling.py +41 -13
  237. package/md_cg/test_p44_md_whitebox.py +14 -1
  238. package/md_cg/test_protocol.py +243 -0
  239. package/md_cg/test_reach.py +378 -0
  240. package/md_cg/test_reach_keys.py +201 -0
  241. package/md_cg/test_reach_meta_exits.py +145 -0
  242. package/md_cg/test_read_clip.py +141 -0
  243. package/md_cg/test_retr_s1.py +340 -0
  244. package/md_cg/test_retr_s1b.py +209 -0
  245. package/md_cg/test_retr_s3.py +194 -0
  246. package/md_cg/test_retr_s4.py +163 -0
  247. package/md_cg/test_retr_s5.py +200 -0
  248. package/md_cg/test_retr_s6.py +157 -0
  249. package/md_cg/test_retr_s7.py +385 -0
  250. package/md_cg/test_retr_s8_time.py +316 -0
  251. package/md_cg/test_retr_s9_edges.py +286 -0
  252. package/md_cg/test_retr_s9_entity_ctx.py +175 -0
  253. package/md_cg/test_review_conformance.py +367 -0
  254. package/md_cg/test_role_views.py +354 -0
  255. package/md_cg/test_tasks.py +409 -0
  256. package/md_cg/test_tool_face.py +189 -0
  257. package/md_cg/test_trust.py +361 -0
  258. package/md_cg/test_twophase.py +286 -0
  259. package/md_cg/test_units_poll.py +71 -0
  260. package/md_cg/test_v14_fixes.py +397 -0
  261. package/md_cg/test_validity_filter.py +280 -0
  262. package/md_cg/test_wisdom_md_store.py +7 -3
  263. package/md_cg/test_writepipe.py +214 -0
  264. package/md_cg/theory.py +17 -2
  265. package/md_cg/tokens.py +134 -12
  266. package/md_cg/tool_face.py +261 -0
  267. package/md_cg/trust.py +943 -0
  268. package/md_cg/twophase.py +232 -0
  269. package/md_cg/units.py +665 -0
  270. package/md_cg/vision_evidence.py +25 -2
  271. package/md_cg/weights.py +25 -2
  272. package/md_cg/whitebox.py +33 -2
  273. package/md_cg/whitebox_kb/data/verify_cache.json +21233 -360
  274. package/md_cg/whitebox_kb/data/verify_savings.jsonl +5124 -0
  275. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  276. package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
  277. package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
  278. package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
  279. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
  280. package/md_cg/writelimit.py +37 -7
  281. package/md_cg/writepipe.py +543 -0
  282. package/package.json +2 -2
  283. package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
  284. package/skills/skills/designer-perspective/scripts/designer.py +17 -1
  285. package/skills/skills/designer-perspective/tests/selftest.py +3 -1
  286. package/src/hooks.ts +27 -22
  287. package/src/index.ts +33 -6
  288. package/src/lib/datapath.ts +211 -13
  289. package/src/lib/mdcg_client.ts +12 -8
  290. package/src/lib/mutual.ts +411 -411
  291. package/src/lib/prompt_safety.ts +62 -0
  292. package/src/lib/token_store.ts +4 -5
  293. package/zcode/AGENTS.md +21 -10
  294. package/zcode/README.md +4 -0
  295. package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
  296. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
  297. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
  298. /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
  299. /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
  300. /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
  301. /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
  302. /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
  303. /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
  304. /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
  305. /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
  306. /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
  307. /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
  308. /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
  309. /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
  310. /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
  311. /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
  312. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
  313. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
  314. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
  315. /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
  316. /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
  317. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
  318. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
  319. /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
  320. /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
  321. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
  322. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
  323. /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
  324. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
  325. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
@@ -52,11 +52,13 @@ PROTOCOL_VERSION = "2024-11-05"
52
52
  # 工具定义
53
53
  # --------------------------------------------------------------------------
54
54
 
55
+ # 生效条件:传 _desc 与任意 props 时返回 {"type":"object","properties":props,"required":[...]},required 仅列出 props 中值含真值 "_req" 键的条目名。
55
56
  def _s(_desc, **props):
56
57
  return {"type": "object", "properties": props, "required":
57
58
  [k for k, v in props.items() if v.get("_req")]}
58
59
 
59
60
 
61
+ # 生效条件:始终返回 {"type":t,"description":desc},仅当 req 为真值时该字典额外含 "_req": True。
60
62
  def _p(t, desc, req=False):
61
63
  d = {"type": t, "description": desc}
62
64
  if req:
@@ -64,6 +66,7 @@ def _p(t, desc, req=False):
64
66
  return d
65
67
 
66
68
 
69
+ # 生效条件:exp 为假值(None/空容器等)时返回 None,exp 为真值时返回 _expand 闭包——该闭包调用时先取 expand_query_terms_weighted(q),再对 exp 每项(dict 项 term=str(it.get("term") or "").strip()、w=float(it.get("weight",0.5)) 抛 TypeError/ValueError 则 w=0.5;非 dict 项 term=str(it).strip()、w=0.5)在 term 非空时写 out[term]=max(out.get(term,0.0), max(0.0,min(1.0,w))),最后置 out["__source__"]="llm" 并返回 out;
67
70
  def _make_query_expand(exp):
68
71
  """把调用方(LLM/多智能体)提供的扩展词包装成 query_expand 注入函数。
69
72
 
@@ -74,6 +77,7 @@ def _make_query_expand(exp):
74
77
  return None
75
78
  from .mdcg import expand_query_terms_weighted
76
79
 
80
+ # 生效条件:在外层 _make_query_expand(exp) 的 exp 为真值前提下被调用时,先 out=expand_query_terms_weighted(q),再对 _exp(默认绑定外层 exp)每项——dict 项取 term=str(it.get("term") or "").strip()、w=float(it.get("weight",0.5))(抛 TypeError/ValueError 则 w=0.5),非 dict 项取 term=str(it).strip()、w=0.5,term 非空时 out[term]=max(out.get(term,0.0), max(0.0,min(1.0,w))),最后置 out["__source__"]="llm" 并返回 out。
77
81
  def _expand(q, _exp=exp):
78
82
  out = expand_query_terms_weighted(q)
79
83
  for it in _exp:
@@ -138,6 +142,11 @@ TOOLS = [
138
142
  "仅在查询时刻生效,索引侧仍白箱"),
139
143
  session=_p("string", "会话归属过滤(frontmatter.session;"
140
144
  "多会话共用 root 时只取本会话记忆;缺省不过滤)"),
145
+ validity=_p("boolean", "时效过滤(**显式启用**,缺省不过滤):"
146
+ "仅排除**已过期**(valid_until 已过)节点;"
147
+ "**未生效(valid_from 未到)一律保留**——"
148
+ "「尚未开始」与「已失效」语义相反,预约/计划类"
149
+ "记忆在生效前仍可召回"),
141
150
  goal=_p("string", "当前目标(第 5 篇第 3 章):启用 goal 路给召回定向;"
142
151
  "省略则自动取活跃目标"),
143
152
  goal_path=_p("boolean", "启用目标定向路(默认否;给 goal 即自动启用)"),
@@ -155,7 +164,10 @@ TOOLS = [
155
164
  layer=_p("string", "限定层"), context=_p("object", "情境"),
156
165
  roles=_p("array", "限定角色"), include_work=_p("boolean", "含工作角色"),
157
166
  session=_p("string", "会话归属过滤(frontmatter.session;"
158
- "缺省不过滤)")),
167
+ "缺省不过滤)"),
168
+ validity=_p("boolean", "时效过滤(显式启用,缺省不过滤):仅排除"
169
+ "**已过期**(valid_until 已过)节点,"
170
+ "未生效(valid_from 未到)保留")),
159
171
  },
160
172
  {
161
173
  "name": "mdcg_get",
@@ -213,11 +225,13 @@ TOOLS = [
213
225
  },
214
226
  {
215
227
  "name": "mdcg_review_decide",
216
- "description": "审核裁决:accept / reject / edit / merge(merge 需 merge_into)。"
228
+ "description": "审核裁决:accept / reject / edit / merge / noop(merge 需 merge_into)。"
229
+ "noop=已评估、判定不改变任何现有记忆:只留痕并关闭提案,"
230
+ "不落业务节点、不进负记忆(与 reject 的区别是语义而非路径)。"
217
231
  "redteam.verdict=reject 不落库,转 needs_reapproval,"
218
232
  "修复后须带递增 round 的 pass 再审批;判据只读不可改。",
219
233
  "inputSchema": _s("", pid=_p("string", "提案 id", True),
220
- decision=_p("string", "accept|reject|edit|merge", True),
234
+ decision=_p("string", "accept|reject|edit|merge|noop", True),
221
235
  edits=_p("object", "edit 时的覆盖字段"), merge_into=_p("string", "merge 目标节点 id"),
222
236
  reason=_p("string", "裁决理由"),
223
237
  redteam=_p("object", "红队裁决 {verdict:pass|reject, issues:[], round:n}"),
@@ -307,13 +321,16 @@ TOOLS = [
307
321
  "情绪=信息差二阶变化 d²D/dt²(§十一)→ 轨迹面;"
308
322
  "自信校准(期望正确率 vs 实际验证通过率,过度自信/过度保守)→ 校准面;"
309
323
  "盲区地图(反复 BLINDSPOT 的查询邻域 + 未解问题,推论三)→ 盲区面;"
310
- "P_gap/P_trust + 情感 d²T/dt²(§十)→ 信任面。"
324
+ "P_gap/P_trust + 情感 d²T/dt²(§十)→ 信任面;"
325
+ "D_meta 边界压力向量(events_pressure / unmodeled_growth / "
326
+ "boundary_violation_rate 各自 [0,1],**不合成单值**,DEV-002a)"
327
+ "→ 边界面。"
311
328
  "action=report 完整报告(含确定性建议)/ trace / calibration / "
312
329
  "blindspots / trust / self_check(回答前自检:该直接答还是先补条件)"
313
- " / history / catalog。只读留痕,不改事实层。",
330
+ " / history / d_meta / catalog。只读留痕,不改事实层。",
314
331
  "inputSchema": _s("", action=_p("string", "report|trace|calibration|"
315
332
  "blindspots|trust|self_check|"
316
- "history|catalog"),
333
+ "history|d_meta|catalog"),
317
334
  query=_p("string", "self_check 的待答查询"),
318
335
  k=_p("integer", "self_check:相似历史条数"),
319
336
  window=_p("integer", "轨迹/信任的滚动窗口"),
@@ -496,7 +513,9 @@ KERNEL_TOOLS = [
496
513
  {
497
514
  "name": "cg",
498
515
  "description": "认知图接口(唯一入口)。op=route:按情境条件路由,返回相关知识 + "
499
- "建议能力名(不执行,由调用方决定);op=read:召回/检索/按 id 取;"
516
+ "建议能力名(不执行,由调用方决定);op=read:召回/检索/按 id 取"
517
+ "(长内容自动截断 2000 行 / 50KB,返回 truncated+next_offset,"
518
+ "续读传 offset=next_offset,不静默丢内容);"
500
519
  "op=write:写入前按 content_kind 审核(ACCEPT 落盘 / REJECT 进负记忆 / "
501
520
  "DEFER 进审核队列)+ 节点间冲突检测(三级决策:情绪→反思→递归反思;"
502
521
  "consistency=false 可关,on_conflict=reject|defer|record);"
@@ -507,6 +526,12 @@ KERNEL_TOOLS = [
507
526
  "op=forget:软删除/恢复(受保护节点需 override);"
508
527
  "op=goal:目标槽(第 5 篇七件套之「目标」,"
509
528
  "action=add|list|status;目标不进召回,只给 read 定向);"
529
+ "op=task:结构层任务实体(跨会话的工程台账,与信任/自我/协议同级;"
530
+ "action=open 登记或更新(同名即同卡)、status 状态迁移"
531
+ "(进行中/受阻/完成/放弃,迁 完成 时结果必填)、plan_add 追加计划变更、"
532
+ "get 单卡全字段、list 清单、find 同族提示(只提示不合并)、"
533
+ "session 会话装配面(进行中+近期完成);"
534
+ "任务只存骨架,中间过程不入库,任务知识走 knowledge 层);"
510
535
  "op=recent:近期事件窗口(七件套之「近期事件」,"
511
536
  "action=add|list|clear,滚动保留最近 N 条);"
512
537
  "op=identity:身份特征识别(智能论 v3.4 位置效应 + 扮演论"
@@ -562,16 +587,59 @@ KERNEL_TOOLS = [
562
587
  "另有节点**派生溯源**(区别于对端信任):derive 列边、"
563
588
  "derive_dangling 悬空巡检(只读、不删边)、derive_catalog 自描述、"
564
589
  "derive_rebuild 按 frontmatter 重建台账(默认预演);权威声明只在"
565
- "节点写入时产生(write/remember 的 derived_from),本层不新增写入口)。",
590
+ "节点写入时产生(write/remember 的 derived_from),本层不新增写入口);"
591
+ "op=session:会话三件套(action=note 写要点 / recall 续接 / "
592
+ "compact 压摘要;hook 缺失时的库侧替代——载体负责「何时做」、库保证"
593
+ "「一次调用就够用」;note 与 compact(note=True) 需 can_write,recall 只读);"
594
+ "op=ingest:文件摄取(action=file|dir|jsonl|stat;写链需 can_write,"
595
+ "支持 dry_run 预演与 incremental 增量去重、watermark 留痕);"
596
+ "op=export:全库导出(action=graph|nodes|slice|stat;导出整库属管理"
597
+ "操作,一律 require_admin);"
598
+ "op=maintain:记忆维护(action=stat|history|importance|longterm|"
599
+ "prefeed|separate|rollback|backfill|cap|exempt|vision_evidence|refine|"
600
+ "propagate 及其 *_history;propagate = 验证态的**多跳**失效传播巡检"
601
+ "(默认预演,apply 需 admin);权限按 action 分档:只读放行、prefeed "
602
+ "写入需 can_write、批量改写与 rollback 需 admin;dry-run 只出报表不改盘);"
603
+ "op=consolidate:离线固化(action=promote 提升 | induce 归纳 | "
604
+ "contextualize 语境化,及其 _rollback/_history;批量提升属管理操作,"
605
+ "一律 require_admin);"
606
+ "op=insight:洞察(action=window|record|verify|list|report|"
607
+ "reconstruct|learn|outlook|catalog|fork|branch_rewrite|branch_search|"
608
+ "branch_merge|branch_discard|branches|tickets;权限按 action 分档:只读放行、"
609
+ "条件记账与分支写需 can_write、落库 apply 与分支弃置需 admin);"
610
+ "op=ccg:CCG 六要素编译器(action=compile|review|attest|link|"
611
+ "recalibrate|units|catalog;入参统一在 ccg 对象里)。"
612
+ "把对话记录编译为六要素候选 → **编外复核**(裁定 A:编译者不得自证,"
613
+ "E041 机械拒绝)→ 落库;复核优先走蜂巢 reflect/verify 单元,"
614
+ "蜂巢不可用则提示配置,或显式 allow_degrade 降级 harness 端子代理。"
615
+ "op=status:**可验证记忆单元**读面(验证态 unverified|verified|"
616
+ "doubted|expired|rechecking + 依赖 depends_on + 双时间轴 valid_from/"
617
+ "valid_until + 履历台账);给 node_id 看单节点全貌、不给看全库摘要、"
618
+ "action=ledger 读台账。所有读面返回体统一带 status_head 状态头"
619
+ "(✓已验证 / △已修改 / !异常 / ?存疑),hint 亦升级为该格式"
620
+ "(MDCG_STATUS_HEAD=0 回退旧文本)。"
621
+ "op=edges:**三元组反查**读面(阶段二 4.2):按派生边任意端 / "
622
+ "谓词 / 时间反查「这条记忆从哪来、谁由它派生」——subject/predicate/"
623
+ "object ≡ child/relation/parent(`derived_from` 派生的三元组)。"
624
+ "支持排序(ordering=desc 按写入时刻新→旧,缺省/asc)、分页"
625
+ "(offset/limit)、**分页前全集**聚合(aggregation=by_relation|"
626
+ "by_parent|by_child)、端点索引摘要(expand_nodes=true)。"
627
+ "时间条件缺省走**观察轴**(派生边只有写入时刻 t,无效力声明)"
628
+ "——与 op=read 缺省效力轴**有意不同**,由库层单点校验。",
566
629
  "inputSchema": _s("",
567
630
  op=_p("string", "route|read|write|verify|review|protect|identity|"
568
631
  "consistency|metacognition|self_state|evolution|sustain|"
569
632
  "scrub|predict|causal|"
570
- "forget|goal|recent|info|index_code|index_doc|ref|whitebox|"
633
+ "forget|goal|task|recent|info|index_code|index_doc|ref|whitebox|"
571
634
  "theory|link|session|ingest|export|maintain|consolidate|"
572
- "insight", True),
635
+ "insight|ccg|status|edges|help", True),
636
+ ccg=_p("object", "CCG 六要素编译器入参:{action, node_id, dialog, marks, "
637
+ "slots, strict_spans, role, verdict, verifier, compiled_by, "
638
+ "evidence, slot_corrections, model, jobs, blocking, wait_s, "
639
+ "allow_degrade, channel, autostart, doctor, apply, basis}"),
573
640
  intent=_p("string", "route 的查询意图"), query=_p("string", "read 的查询"),
574
- goal=_p("string", "goal op 的目标文本;read 的定向目标(缺省用活跃目标)"),
641
+ goal=_p("string", "goal op 的目标文本;read 的定向目标(缺省用活跃目标);"
642
+ "task op:任务目标(CCG「执行」栏)"),
575
643
  goal_status=_p("string", "goal op:active|done|dropped"),
576
644
  priority=_p("number", "goal op:优先级 0-1(兼作定向偏置依据)"),
577
645
  deadline=_p("string", "goal op:截止时间(仅排序用,不做硬约束)"),
@@ -583,10 +651,34 @@ KERNEL_TOOLS = [
583
651
  meta=_p("object", "recent op:附加元数据"),
584
652
  window=_p("integer", "recent op:滚动窗口大小(默认 200)"),
585
653
  include_recent=_p("boolean", "read:是否附近期事件窗口(默认否)"),
586
- limit=_p("integer", "goal/recent 的返回条数;read 的近期事件条数"),
654
+ limit=_p("integer", "goal/recent 的返回条数;read 的近期事件条数;"
655
+ "edges 分页条数(缺省 50、上限 500,0/负数报错不当「全部」)"),
587
656
  node_id=_p("string", "节点 id"),
657
+ offset=_p("integer", "read 的续读起始行(1 基;传上次返回的 next_offset);"
658
+ "edges 的分页偏移(排序后切片,默认 0)"),
588
659
  content=_p("string", "write 的内容(建议含 CCG 5 要素注释)"),
589
- content_kind=_p("string", "write 的内容类型:code|image_desc|text|permission|work_done|work_wip"),
660
+ content_kind=_p("string", "write 的内容类型:code|image_desc|text|permission|work_done|work_wip|ccg_marks|hyperedge"),
661
+ depends_on=_p("array", "write/verify:本单元**依赖**的节点 id 列表(CCG「子功能」"
662
+ "的落字段,单值/逗号串亦可)。被依赖单元被修改或被证伪时,"
663
+ "本节点**同跳**标「存疑」(一跳同步;多跳走 maintain"
664
+ " action=propagate)。**何时需要它**:正文「# 子功能:」行"
665
+ "以 @<节点 id> 显式引用其它单元时(仅自述本单元内部构成"
666
+ "不算依赖,无需本字段)——显式引用却无可解析目标 → 硬拒"
667
+ "(E050 缺 depends_on / E051 目标悬空)"),
668
+ valid_from=_p("string", "双时间轴**起点**(ISO8601,如 2026-01-01):此刻起才成立"
669
+ "(未到点在 scrub 报 not_yet,仅提示不降权)"),
670
+ valid_until=_p("string", "双时间轴**终点**(ISO8601):此刻后不再成立"
671
+ "(过期在 scrub 报 expired,weaken/demote)"),
672
+ start_time=_p("string", "时间算子:查询窗起点(ISO8601/时间戳)。启用后 "
673
+ "read 按 time_axis 轴把候选收敛到窗内;edges 按边"
674
+ "写入时刻 t 收敛(**edges 缺省轴 observed**,与 read 不同)"),
675
+ end_time=_p("string", "时间算子:查询窗终点(缺省该侧=无界)"),
676
+ start_operator=_p("string", "时间算子:起点比较 gt|gte|eq|lte|lt"
677
+ "(两端都不给算子=区间重叠语义)"),
678
+ end_operator=_p("string", "时间算子:终点比较 gt|gte|eq|lte|lt"),
679
+ time_axis=_p("string", "时间算子轴:effective(效力轴,read 缺省)|"
680
+ "observed(观察轴 temporal/time_window;"
681
+ "**edges 的缺省轴**——派生边只有记录时刻,无效力声明)"),
590
682
  layer=_p("string", "层:anchor|structural|knowledge|contextual|self"),
591
683
  tags=_p("array", "标签(cap:xxx 会作为 route 的建议能力名)"),
592
684
  importance=_p("number", "重要性 0-1"),
@@ -610,6 +702,11 @@ KERNEL_TOOLS = [
610
702
  non_applicable_conditions=_p("array", "不适用条件"),
611
703
  context=_p("object", "当前情境"),
612
704
  k=_p("integer", "返回条数(sustain pool_bench 亦用)"),
705
+ view=_p("string", "read/route 的角色化读取视图(第四阶段 6.1,缺省关):"
706
+ "main(主代理:结论与修正历史)|"
707
+ "verifier(验证端:判据面与环境陷阱)|"
708
+ "receipt(回执审计:命令与预期输出);"
709
+ "非法值库层报错(fail-closed)"),
613
710
  budget_tokens=_p("integer", "read 的 token 预算"),
614
711
  evidence=_p("string", "verify 的证据"),
615
712
  verdict=_p("string", "verify 裁决:confirmed|weakened|falsified;"
@@ -620,7 +717,7 @@ KERNEL_TOOLS = [
620
717
  marker=_p("string", "whitebox verify_encoding:唯一口令标记(缺省自动生成)"),
621
718
  fact=_p("string", "whitebox verify_encoding:待编码事实"),
622
719
  questions=_p("array", "whitebox verify_existing:探针问题列表"),
623
- action=_p("string", "review: list|decide|rounds;forget: forget|restore;"
720
+ action=_p("string", "review: list|decide|rounds|stats;forget: forget|restore;"
624
721
  "protect: stats|check|mark|snapshot|history|forgetting;"
625
722
  "identity: observe|anchor|trait|profile|positions|catalog;"
626
723
  "consistency: check|history|stats|catalog;"
@@ -652,7 +749,15 @@ KERNEL_TOOLS = [
652
749
  "promote_history|induce|contextualize|"
653
750
  "contextualize_rollback|contextualize_history;"
654
751
  "insight: window|record|verify|list|report|"
655
- "reconstruct|learn|outlook|catalog"),
752
+ "reconstruct|learn|outlook|catalog|"
753
+ "fork|branch_rewrite|branch_search|"
754
+ "branch_merge|branch_discard|branches|"
755
+ "tickets;"
756
+ "分支六 act:node_ids/branch_id/note/content/"
757
+ "reason 按 act 取用(rewrite 传 node_id+content);"
758
+ "tickets:盲区→四类消解票据任务卡"
759
+ "(types=[research|prototype|grilling|task],"
760
+ "min_blindspot 资格线;apply=True 落库需 admin)"),
656
761
  pid=_p("string", "review decide 的提案 id"),
657
762
  decision=_p("string", "review 裁决:accept|reject|edit|merge"),
658
763
  edits=_p("object", "review edit 的覆盖字段(不可含 verify)"),
@@ -686,10 +791,15 @@ KERNEL_TOOLS = [
686
791
  "路径命中私有提示时保守降为 private)"),
687
792
  ref=_p("object", "ref op:直接给 code_ref/doc_ref 对象(与 node_id 二选一)"),
688
793
  root=_p("string", "ref op:覆盖 ref 里记录的 root(索引结果的跨机器搬迁)"),
689
- name=_p("string", "sustain:心跳名(默认 md_cg)"),
794
+ name=_p("string", "sustain:心跳名(默认 md_cg);"
795
+ "task op:任务名(**身份判据**:同名即同任务,slug 归一后落 id)"),
690
796
  session=_p("string", "read(search/recall 分支):会话归属过滤"
691
797
  "(frontmatter.session;缺省不过滤);"
692
798
  "sustain:会话 id(resume/note 用)"),
799
+ validity=_p("boolean", "read(search/recall 分支):时效过滤(显式启用,"
800
+ "缺省不过滤)——仅排除**已过期**(valid_until 已过)"
801
+ "节点;**未生效(valid_from 未到)保留**"
802
+ "(两者语义相反,预约/计划类记忆生效前仍可召回)"),
693
803
  ts=_p("number", "sustain note:事件时间戳"),
694
804
  seq=_p("integer", "sustain note:事件序号"),
695
805
  task_running=_p("boolean", "sustain:任务执行中(心跳阈值放宽)"),
@@ -729,7 +839,8 @@ KERNEL_TOOLS = [
729
839
  hit=_p("boolean", "predict feedback:是否命中(缺省按 predicted==actual)"),
730
840
  rule=_p("string", "evolution record:规律(必填,一句话认知规律)"),
731
841
  missing=_p("string", "evolution record:补的是哪一维缺失条件"),
732
- change=_p("string", "evolution record:这次具体改了什么"),
842
+ change=_p("string", "evolution record:这次具体改了什么;"
843
+ "task op:本轮计划变更/新增问题(追加进「计划变更」节,可追溯)"),
733
844
  kind=_p("string", "evolution:condition_gap|layer_shift|general"),
734
845
  entry_id=_p("string", "evolution show/rollback:条目 id"),
735
846
  source=_p("string", "evolution record:来源 consolidate|verify|manual;"
@@ -768,14 +879,22 @@ KERNEL_TOOLS = [
768
879
  verdicts=_p("array", "maintain refine apply:人工核对裁决 "
769
880
  "[{concept_id, faithful, added_info, note}];"
770
881
  "缺省只落抽检留痕(不改节点)"),
771
- batch=_p("string", "maintain/consolidate:批次号(回滚用)"),
882
+ batch=_p("string", "maintain/consolidate:批次号(回滚用);"
883
+ "edges:按派生边批次号过滤"),
772
884
  derived_from=_p("string", "write/remember:派生来源节点 id(G8,可用逗号/空格"
773
885
  "分隔多个);声明后写入 frontmatter 并建派生边"),
774
- relation=_p("string", "write/remember/link derive:派生关系名,"
886
+ relation=_p("string", "write/remember/link derive/edges:派生关系名(**谓词**),"
775
887
  "默认 derived_from(split_from|extracted_from|"
776
888
  "merged_from|refined_from|source)"),
777
- child=_p("string", "link derive:按子节点 id 过滤派生边"),
778
- parent=_p("string", "link derive:按父节点 id 过滤派生边"),
889
+ child=_p("string", "link derive/edges:按子节点 id 过滤派生边"
890
+ "(edges 中即三元组的**主体**)"),
891
+ parent=_p("string", "link derive/edges:按父节点 id 过滤派生边"
892
+ "(edges 中即三元组的**对象**)"),
893
+ ordering=_p("string", "edges:排序方向 desc(按写入时刻 新→旧,缺省)|asc"),
894
+ aggregation=_p("string", "edges:分页前全集分桶 by_relation|by_parent|"
895
+ "by_child(缺省不聚合)"),
896
+ expand_nodes=_p("boolean", "edges:是否附端点索引摘要 child_node/"
897
+ "parent_node(默认否;只读索引快照)"),
779
898
  ledger_only=_p("boolean", "link derive_dangling:只看台账、忽略索引声明"),
780
899
  pools=_p("object", "search/sustain pool_bench:召回分池表"
781
900
  "{knowledge:{cap_ratio,weight},index:{…},"
@@ -810,21 +929,37 @@ KERNEL_TOOLS = [
810
929
  neighbors=_p("boolean", "insight reconstruct:是否并入一跳邻域(默认 true)"),
811
930
  recent_days=_p("integer", "insight outlook:近期窗口天数(默认 7)"),
812
931
  sample_limit=_p("integer", "insight outlook:抽样清单条数(默认 8)"),
813
- max_events=_p("integer", "ingest:单次最多摄取事件数")),
932
+ max_events=_p("integer", "ingest:单次最多摄取事件数"),
933
+ task_status=_p("string", "task op:active|blocked|done|dropped;"
934
+ "迁 done 必须同时给 result(缺一不收)"),
935
+ plan=_p("string", "task op:分步计划(每轮覆盖更新,跨会话持久存在)"),
936
+ result=_p("string", "task op:任务结果(done 时必填;本次不传则**保留旧值**,"
937
+ "不会被静默清空)"),
938
+ acceptance=_p("string", "task op:验收判据(怎么算完成)"),
939
+ boundary=_p("string", "task op:不适用条件/边界"),
940
+ note=_p("string", "task op:本轮备注(change 是计划变更,note 是附注);"
941
+ "self_state relate:备注"),
942
+ condition=_p("string", "task op:生效条件(CCG「生效条件」栏);"
943
+ "self_state relate:关系成立条件"),
944
+ active_limit=_p("integer", "task action=session:进行中任务条数(默认 5)"),
945
+ done_limit=_p("integer", "task action=session:近期已完成条数(默认 5)")),
814
946
  },
815
947
  {
816
948
  "name": "stg",
817
949
  "description": "语义时空图接口:精确得到信息的时间/空间关系。"
818
950
  "op=relation:两节点时空关系(Allen 时间 6 态 + RCC 空间 7 态);"
819
951
  "op=timeline:按时间排序;op=anchors:落在时间窗/空间范围的节点;"
820
- "op=consistency:时空字段自洽性检查。",
952
+ "op=consistency:时空字段自洽性检查。"
953
+ "四个 op 均可用 time_axis 切换时间轴(缺省 observed)。",
821
954
  "inputSchema": _s("",
822
955
  op=_p("string", "relation|timeline|anchors|consistency", True),
823
956
  a=_p("string", "relation 的节点 a"), b=_p("string", "relation 的节点 b"),
824
957
  time_window=_p("array", "anchors 的时间窗 [t1,t2]"),
825
958
  bbox=_p("array", "anchors 的包围盒 [x1,y1,x2,y2]"),
826
959
  layer=_p("string", "限定层"), limit=_p("integer", "返回条数"),
827
- desc=_p("boolean", "timeline 是否倒序(默认是)")),
960
+ desc=_p("boolean", "timeline 是否倒序(默认是)"),
961
+ time_axis=_p("string", "时间轴:observed(观察轴 temporal/time_window,"
962
+ "缺省)| effective(效力轴 effective_from/until)")),
828
963
  },
829
964
  ]
830
965
 
@@ -832,19 +967,30 @@ ALL_TOOLS = KERNEL_TOOLS + TOOLS
832
967
  SURFACE = os.environ.get("MDCG_MCP_SURFACE", "kernel").strip().lower()
833
968
 
834
969
 
970
+ # 生效条件:模块级 SURFACE 等于 "full" 时以 ALL_TOOLS、否则以 KERNEL_TOOLS 调 slim_tools 并返回其结果。
835
971
  def tools_for_surface():
836
- """kernel:只暴露 2 个基元;full:2 个基元 + 31 个细粒度工具(兼容/调试)。"""
837
- return ALL_TOOLS if SURFACE == "full" else KERNEL_TOOLS
972
+ """kernel:只暴露 2 个基元;full:2 个基元 + 31 个细粒度工具(兼容/调试)。
973
+
974
+ 返回**投影后**的工具面(工具一行职责 + 参数短提示):完整语义由
975
+ ``cg(op=help, query=…)`` 按需取回。真源 KERNEL_TOOLS/TOOLS 保持完整定义,
976
+ 投影只是运行时视图(信息零丢失,见 md_cg/tool_face.py)。
977
+ 关闭投影:``MDCG_TOOL_FACE=full``。
978
+ """
979
+ tools = ALL_TOOLS if SURFACE == "full" else KERNEL_TOOLS
980
+ from .tool_face import slim_tools
981
+ return slim_tools(tools)
838
982
 
839
983
 
840
984
  # --------------------------------------------------------------------------
841
985
  # 序列化
842
986
  # --------------------------------------------------------------------------
843
987
 
988
+ # 生效条件:始终返回 json.dumps(obj, ensure_ascii=False, default=str)。
844
989
  def _j(obj) -> str:
845
990
  return json.dumps(obj, ensure_ascii=False, default=str)
846
991
 
847
992
 
993
+ # 生效条件:path 以 ".zstd" 结尾时直接返回 DSHSessionSource(path);否则读首个非空行做 json.loads,该 dict 满足 o.get("type")=="session" 或同时含 "type"/"seq"/"data" 时返回 DSHSessionSource(path),能解析但不满足时返回 JsonlSource(path),json.loads 抛 ValueError 或 open/读抛 OSError 或无非空行时落到返回 JsonlSource(path)。
848
994
  def _pick_source(path):
849
995
  """按内容嗅探源类型:DSH 会话格式 vs 通用 JSONL。"""
850
996
  from .sources import DSHSessionSource, JsonlSource
@@ -866,17 +1012,80 @@ def _pick_source(path):
866
1012
  return JsonlSource(path)
867
1013
 
868
1014
 
869
- def _node_view(node):
1015
+ READ_MAX_LINES = 2000 # 单次 read 返回行数上限(Pi⑦② 截断)
1016
+ READ_MAX_BYTES = 50 * 1024 # 单次 read 返回字节上限(Pi⑦② 截断)
1017
+
1018
+
1019
+ # 生效条件:text 先按 text or "" 归一,start 由 offset 定(offset 为假值取 0,否则 max(0,int(offset)-1));start≥total_lines 时返回空 text、next_offset=None 的窗口结果;否则从 start 起按 max_lines 行收集(max_lines=0 时 got 为空),且仅当已收集非空且 used+b>max_bytes 时提前 break(首行即便超 max_bytes 也收),end<total_lines 时 truncated=True、next_offset=end+1 并写续读 note。
1020
+ def _clip_text(text: str, *, offset: int = 0, max_lines: int = READ_MAX_LINES,
1021
+ max_bytes: int = READ_MAX_BYTES) -> dict:
1022
+ """行窗口 + 双阈值截断(Pi⑦② 截断必带续读提示)。
1023
+
1024
+ offset 为 1 基起始行(续读传上次 next_offset)。不静默丢内容——截断时
1025
+ 返回 total_lines / total_bytes / next_offset / note,调用方按 offset 续读。
1026
+ """
1027
+ text = text or ""
1028
+ lines = text.split("\n")
1029
+ total_lines = len(lines)
1030
+ total_bytes = len(text.encode("utf-8"))
1031
+ start = max(0, int(offset) - 1) if offset else 0
1032
+ if start >= total_lines:
1033
+ return {"text": "", "offset": start + 1, "next_offset": None,
1034
+ "returned_lines": 0, "total_lines": total_lines,
1035
+ "total_bytes": total_bytes, "truncated": False,
1036
+ "note": f"offset={start + 1} 超出总行数 {total_lines},返回空"}
1037
+ got, used = [], 0
1038
+ for ln in lines[start:start + max_lines]:
1039
+ b = len(ln.encode("utf-8")) + 1
1040
+ if got and used + b > max_bytes:
1041
+ break
1042
+ got.append(ln)
1043
+ used += b
1044
+ end = start + len(got)
1045
+ truncated = end < total_lines
1046
+ out = {"text": "\n".join(got), "offset": start + 1,
1047
+ "next_offset": end + 1 if truncated else None,
1048
+ "returned_lines": len(got), "total_lines": total_lines,
1049
+ "total_bytes": total_bytes, "truncated": truncated, "note": ""}
1050
+ if truncated:
1051
+ out["note"] = (f"内容已截断(单次上限 {max_lines} 行 / {max_bytes} 字节):"
1052
+ f"共 {total_lines} 行 / {total_bytes} 字节,本次返回第 "
1053
+ f"{start + 1}–{end} 行;续读传 offset={end + 1}。")
1054
+ return out
1055
+
1056
+
1057
+ # 生效条件:node 为假值时返回 None;否则用 _clip_text(node.get("content") or "", offset=offset) 构造 {id,path,frontmatter,content},且当 clip["truncated"] 为真或 offset 为真值时追加 truncated/content_lines/content_bytes/offset/next_offset/note。
1058
+ def _trust_state(fm):
1059
+ """节点验证态(缺字段按 unverified;导入失败不拖垮读面)。"""
1060
+ try:
1061
+ from . import trust
1062
+ return trust.state_of(fm)
1063
+ except Exception: # noqa: BLE001
1064
+ return None
1065
+
1066
+
1067
+ def _node_view(node, offset: int = 0):
870
1068
  if not node:
871
1069
  return None
872
- return {"id": node.get("id"), "path": node.get("path"),
873
- "frontmatter": node.get("frontmatter"), "content": node.get("content")}
1070
+ clip = _clip_text(node.get("content") or "", offset=offset)
1071
+ out = {"id": node.get("id"), "path": node.get("path"),
1072
+ "frontmatter": node.get("frontmatter"), "content": clip["text"],
1073
+ # 可验证记忆单元:读面 additive 透出验证态(**不改排序权重**,保基准)
1074
+ "verification_state": _trust_state(node.get("frontmatter"))}
1075
+ if clip["truncated"] or offset:
1076
+ out.update({"truncated": clip["truncated"],
1077
+ "content_lines": clip["total_lines"],
1078
+ "content_bytes": clip["total_bytes"],
1079
+ "offset": clip["offset"], "next_offset": clip["next_offset"],
1080
+ "note": clip["note"]})
1081
+ return out
874
1082
 
875
1083
 
876
1084
  # --------------------------------------------------------------------------
877
1085
  # 基元实现
878
1086
  # --------------------------------------------------------------------------
879
1087
 
1088
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'stats'(空串/None 回退 'stats')并 strip().lower() 分派:action=stats 返回 cg.protect_stats();action 为 forgetting 或 forgetting_history 返回 {'records': cg.forgetting_history(limit=int(a.get('limit') or 100))}(a.get('limit') 为 0/空串/None 时回落 100);action=check 时 node_id 取 a.get('node_id') or ''(空串/None 回落 ''),返回包含 node_id、exists(nid in (cg.index.get('nodes') or {}))、protected、reason、immutable、immutable_reason 的 dict;action=history 返回 {'node_id': nid, 'versions': protect.history(cg, nid)};action=snapshot 返回 {'node_id': nid, 'snapshot': protect.snapshot(cg, nid)};action 为 mark 或 protect 时若 cg.principal 非 None 则先调用 cg.principal.require_admin('protect_mark'),再返回 protect.mark(cg, nid, a.get('reason') or '显式保护标记')(reason 空串/None 回落 '显式保护标记');其他 action 抛 ValueError;
880
1089
  def _protect_call(cg, a):
881
1090
  """写保护 / 遗忘留痕的统一入口(cg op=protect 与 mdcg_protect 共用)。"""
882
1091
  from . import protect
@@ -904,6 +1113,7 @@ def _protect_call(cg, a):
904
1113
  raise ValueError(f"protect 未知 action:{act}")
905
1114
 
906
1115
 
1116
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'profile'(空串/None 回退 'profile')并 strip().lower() 分派,subject_id 取 a.get('subject_id') or ''(空串/None 回落 ''):action=catalog 返回 identity.catalog();action=positions 返回 {'positions': cg.identity_positions(limit=int(a.get('limit') or 0))}(a.get('limit') 假值回落 0);action=profile 时若 sid 为假值返回 {'subjects': cg.identity_positions(limit=0), 'hint': '指定 subject_id 可获取完整画像(锚点+位置+特征)'},否则返回 cg.identity_profile(sid);action=observe 时以 sid 和 a.get('content') or a.get('text') or '' 调用 cg.identity_observe(含 kind=a.get('subject_kind')、role=a.get('role')、layer=a.get('layer')、tags=a.get('tags')、condition_space=a.get('condition_space')、importance=float(a.get('importance', 0.5))、verification_basis=a.get('verification_basis')、evidence=a.get('evidence')、override=bool(a.get('override')));action=trait 时以 sid 和 a.get('trait') or a.get('content') or '' 调用 cg.identity_trait(含 condition_space=a.get('condition_space')、importance=float(a.get('importance', 0.6))、position=a.get('position')、kind=a.get('subject_kind')、verification_basis=a.get('verification_basis') or 'data'、override=bool(a.get('override')));action=anchor 时若 cg.principal 非 None 先调用 cg.principal.require_admin('identity_anchor'),再以 sid 和 a.get('content') or a.get('text') or '' 调用 cg.identity_anchor(含 kind=a.get('subject_kind')、condition_space=a.get('condition_space')、importance=float(a.get('importance', 0.9))、override=bool(a.get('override'))、requested_layer=a.get('requested_layer'));action=history 返回 {'records': cg.identity_history(limit=int(a.get('limit') or 100))}(a.get('limit') 假值回落 100);其他 action 抛 ValueError;
907
1117
  def _identity_call(cg, a):
908
1118
  """身份特征识别统一入口(cg op=identity 与 mdcg_identity 共用)。
909
1119
 
@@ -954,6 +1164,7 @@ def _identity_call(cg, a):
954
1164
  raise ValueError(f"identity 未知 action:{act}")
955
1165
 
956
1166
 
1167
+ # 生效条件:act=(a.get("action") or "check").strip().lower();"check" 时以 content/text、layer、condition_space、non_applicable_conditions、tags、exclude、limit=int(a.get("limit") or consistency.MAX_SCAN)、depth(非 None 时 int(depth),否则 consistency.MAX_DEPTH)、auto_flywheel 调 cg.check_consistency;"history" 返回 {"records": cg.consistency_history(limit=int(a.get("limit") or 100))};"stats" 返回 cg.consistency_stats();"catalog" 返回 consistency.catalog();其余 act 抛 ValueError。
957
1168
  def _consistency_call(cg, a):
958
1169
  """节点间自动冲突检测统一入口(cg op=consistency 与 mdcg_consistency 共用)。
959
1170
 
@@ -982,6 +1193,7 @@ def _consistency_call(cg, a):
982
1193
  raise ValueError(f"consistency 未知 action:{act}")
983
1194
 
984
1195
 
1196
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'report'(空串/None 回退 'report')分派,window=int(a.get('window') or 50)(a.get('window') 假值回落 50):action=report 返回 cg.metacognition_report(window=window);action=trace 返回 cg.metacognition_trace(window=window);action=calibration 返回 cg.metacognition_calibration(max_scan=int(a.get('limit') or 2000))(a.get('limit') 假值回落 2000);action=blindspots 返回 cg.metacognition_blindspots(limit=int(a.get('limit') or 20), window=window)(a.get('limit') 假值回落 20);action=trust 返回 cg.metacognition_trust(window=window);action=self_check 返回 cg.self_check(a.get('query') or a.get('text') or '', k=int(a.get('k') or 5))(query/text 假值回落 '',k 假值回落 5);action=history 返回 cg.metacognition_history(limit=int(a.get('limit') or 100))(a.get('limit') 假值回落 100);action=catalog 返回 metacognition.catalog();action=d_meta 返回 cg.metacognition_d_meta(window=window)(边界压力向量,三代理各自 [0,1]、不合成单值);其他 action 抛 ValueError;
985
1197
  def _metacognition_call(cg, a):
986
1198
  """独立元认知统一入口(cg op=metacognition 与 mdcg_metacognition 共用)。
987
1199
 
@@ -1011,9 +1223,12 @@ def _metacognition_call(cg, a):
1011
1223
  if act == "catalog":
1012
1224
  from . import metacognition
1013
1225
  return metacognition.catalog()
1226
+ if act == "d_meta": # D_meta 观测面(只读,不合成单值)
1227
+ return cg.metacognition_d_meta(window=window)
1014
1228
  raise ValueError(f"metacognition 未知 action:{act}")
1015
1229
 
1016
1230
 
1231
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'snapshot'(空串/None 回退 'snapshot')分派,subject=a.get('subject') or self_state.DEFAULT_SUBJECT(subject 假值回落常量),session=a.get('session') or getattr(getattr(cg, 'principal', None), 'session', None)(session 假值回落 principal.session):action 为 snapshot/read/get 返回 {'state': self_state.snapshot(cg, subject)};action=refresh 返回 self_state.refresh(cg, subject, window=int(a.get('window') or self_state.RECENT_WINDOW), importance=a.get('importance'), important_refs=a.get('important_refs'), dimensions=a.get('dimensions'), links=a.get('links'), actor=a.get('actor') or getattr(cg, 'actor', 'self_state'), force=bool(a.get('force')), strict=bool(a.get('strict')), session=session)(window 假值回落 self_state.RECENT_WINDOW);action=bootstrap 返回 self_state.bootstrap(cg, subject, window=int(a.get('window') or self_state.RECENT_WINDOW), actor=a.get('actor') or 'bootstrap');action=relate 返回 self_state.relate(cg, a.get('frm') or subject, a.get('to') or '', relation_type=a.get('relation_type') or 'collaborator', strength=a.get('strength', 0.5), condition=a.get('condition') or '', note=a.get('note') or '', reciprocal=bool(a.get('reciprocal')), actor=a.get('actor') or getattr(cg, 'actor', 'self_state'));action=relations 返回 {'relations': self_state.relations(cg, subject=a.get('subject'), direction=(a.get('direction') or 'both'))};action=index 时 dim,value=a.get('dim'),a.get('value'),若 dim 假值且 session 真值则 dim='session' 且 value=value or session,返回 self_state.index(cg, dim, value, limit=int(a.get('limit') or 50), with_content=bool(a.get('with_content')))(limit 假值回落 50);action=dimensions 返回 self_state.dimensions(cg, subject);action=audit 返回 self_state.audit(cg, subject, window=int(a.get('window') or self_state.RECENT_WINDOW));action=history 返回 {'records': self_state.history(cg, limit=int(a.get('limit') or 100), subject=a.get('subject'))}(limit 假值回落 100);action=summary 返回 self_state.summary(cg, subject, session=session);action=catalog 返回 self_state.catalog();其他 action 抛 ValueError;
1017
1232
  def _self_state_call(cg, a):
1018
1233
  """自我状态层统一入口(cg op=self_state 与 mdcg_self_state 共用)。
1019
1234
 
@@ -1075,6 +1290,7 @@ def _self_state_call(cg, a):
1075
1290
  raise ValueError(f"self_state 未知 action:{act}")
1076
1291
 
1077
1292
 
1293
+ # 生效条件:act=(a.get("action") or "routes").strip().lower();act 属 routes/route/predict 时以 start_id=a.get("start_id") or a.get("node_id")、blindspot_id、horizon=int(a.get("horizon") or predict.HORIZON_DEFAULT)、max_branches=int(a.get("max_branches") or predict.MAX_BRANCHES_DEFAULT)、sort=a.get("sort") or "composite"、limit=int(a.get("limit") or 0)、semantic=bool(a.get("semantic", True)) 调 cg.predict_routes;act 属 feedback/rate 时调 cg.predict_feedback;act=="stats" 时调 cg.predict_stats(limit=int(a.get("limit") or 20));act=="catalog" 时返回 predict.catalog();其余 act 抛 ValueError。
1078
1294
  def _predict_call(cg, a):
1079
1295
  """生成式预测统一入口(cg op=predict 与 mdcg_predict 共用)。
1080
1296
 
@@ -1108,6 +1324,7 @@ def _predict_call(cg, a):
1108
1324
  raise ValueError(f"predict 未知 action:{act}")
1109
1325
 
1110
1326
 
1327
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'path'(空串/None 回退 'path')分派,a_id=a.get('a') or a.get('a_id') or a.get('from') or ''(假值链回落 ''),b_id=a.get('b') or a.get('b_id') or a.get('to') or '':action 为 path/reach/reachable 返回 predict.causal_path(cg, a_id, b_id, max_depth=int(a.get('max_depth') or 5))(a.get('max_depth') 假值回落 5);action 为 gate/filter 返回 {'ok': True, 'admitted': predict.causal_gate(cg, a_id, b_id) 的 ok, 'reason': why, 'a': a_id, 'b': b_id, 'note': '语义邻近须能说清关系(因果链/共同父节点/偏好权重>0.5)'};action=chain 返回 cg.causal_chain(a.get('node_id') or a_id, relation_types=a.get('relation_types'), max_depth=int(a.get('max_depth') or chain.MAX_DEPTH_DEFAULT), direction=a.get('direction') or 'out', sort=a.get('sort') or 'strength');action=explain 返回 cg.explain_chain(a.get('node_id') or a_id);action=catalog 返回包含 module/types/chain_types_default/edge_weights/max_depth_default/note/gate/actions 的 dict(gate 取 predict.catalog()['decisions']['D-002']);其他 action 抛 ValueError;
1111
1328
  def _causal_call(cg, a):
1112
1329
  """因果推理统一入口(cg op=causal 与 mdcg_causal 共用)。
1113
1330
 
@@ -1146,6 +1363,7 @@ def _causal_call(cg, a):
1146
1363
  raise ValueError(f"causal 未知 action:{act}")
1147
1364
 
1148
1365
 
1366
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'summary'(空串/None 回退 'summary')分派:action 为 record/add/log 返回 {'entry': cg.evolution_record(node_id=a.get('node_id'), pattern=a.get('rule') or a.get('pattern') or '', missing=a.get('missing') or '', action=a.get('change') or a.get('note') or '', evidence=a.get('evidence') or '', source=a.get('source') or '', kind=a.get('kind') or evolution.KIND_CONDITION_GAP)}(各字段假值回落);action 为 entries/list 返回 cg.evolution_entries(limit=int(a.get('limit') or 50), node_id=a.get('node_id'), kind=a.get('kind'))(a.get('limit') 假值回落 50);action 为 show/get 返回 cg.evolution_show(a.get('entry_id') or '');action=history 返回 cg.evolution_history(a.get('node_id') or '', limit=int(a.get('limit') or 50))(a.get('limit') 假值回落 50);action 为 patterns/regularities 返回 cg.evolution_patterns(limit=int(a.get('limit') or 10))(a.get('limit') 假值回落 10);action=summary 返回 cg.evolution_summary();action 为 rollback/revert/undo 时若 cg.principal 非 None 先调用 cg.principal.require_admin('evolution_rollback'),再返回 cg.evolution_rollback(a.get('entry_id') or '', dry_run=bool(a.get('dry_run')), note=a.get('note') or '');action=catalog 返回 evolution.catalog();其他 action 抛 ValueError;
1149
1367
  def _evolution_call(cg, a):
1150
1368
  """演化账本统一入口(cg op=evolution 与 mdcg_evolution 共用)。
1151
1369
 
@@ -1188,6 +1406,7 @@ def _evolution_call(cg, a):
1188
1406
  raise ValueError(f"evolution 未知 action:{act}")
1189
1407
 
1190
1408
 
1409
+ # 生效条件:value 为 None 时返回 [],value 为 list/tuple/set 时按其元素、其它类型按 str(value).replace(",", " ").split() 取项,再对每项 str(x).strip(),非空且未出现过才按序追加并返回去重列表。
1191
1410
  def _split_ids(value):
1192
1411
  """把 'a,b c' / ['a','b'] / 'a' 统一成去空白的 id 列表(None → [])。
1193
1412
 
@@ -1208,6 +1427,7 @@ def _split_ids(value):
1208
1427
  return out
1209
1428
 
1210
1429
 
1430
+ # 生效条件:始终构造 ex(verify=a.get("verify")、importance=float(a["importance"]) 当 a.get("importance") is not None 否则 None、verification_basis=a.get("verification_basis") or (verdict or {}).get("basis")、non_applicable_conditions、role、derived_from=_split_ids(a.get("derived_from")) or None、relation),返回其中值不属于 (None, [], '', {}) 的键值对。
1211
1431
  def _proposal_extras(a, verdict=None):
1212
1432
  """入队时保全 write 的落盘要素,避免裁决 accept 后退化成默认值。
1213
1433
 
@@ -1235,6 +1455,7 @@ def _proposal_extras(a, verdict=None):
1235
1455
  return {k: v for k, v in ex.items() if v not in (None, [], "", {})}
1236
1456
 
1237
1457
 
1458
+ # 生效条件:act 取 a.get("action") 转 str 去空白并 lower 后缺省 "status",name 取 a.get("name") 或环境变量 MDCG_SUSTAIN_NAME 或 "md_cg";status/info 只读诊断,自愈类 act 只回收派生物(索引重扫、临时分片、日志轮转、心跳戳),永不删除节点、不代签密钥;
1238
1459
  def _sustain_call(cg, a):
1239
1460
  """持续性自维持统一入口(常驻 / 心跳 / 自愈 / 会话续接)。
1240
1461
 
@@ -1343,6 +1564,7 @@ def _sustain_call(cg, a):
1343
1564
  raise ValueError(f"sustain 未知 action:{act}")
1344
1565
 
1345
1566
 
1567
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'sweep'(空串/None 回退 'sweep')分派,ids/kinds 若为字符串则按逗号或空白拆成列表,node_id=a.get('node_id') or a.get('node'),target=ids or ([node_id] if node_id else None)(ids 假值回落 node_id 列表或 None):action 为 sample/spot_check 返回 scrub.sample(cg, int(a.get('k') or a.get('n') or scrub.DEFAULT_SAMPLE), strategy=a.get('strategy') or 'stratified', seed=a.get('seed'))(k/n 假值链回落 scrub.DEFAULT_SAMPLE);action 为 associate/related 时若 node_id 假值抛 ValueError,否则返回 scrub.associate(cg, node_id, hops=int(a.get('hops') or scrub.DEFAULT_HOPS), limit=int(a.get('k') or 30), lexical=bool(a.get('lexical', True)))(hops 假值回落常量,k 假值回落 30,lexical 缺键为 True);action 为 audit/check 返回 scrub.audit(cg, target, hops=int(a.get('hops') or 1), min_severity=a.get('min_severity') or 'info');action 为 decontaminate/repair 返回 scrub.decontaminate(cg, target, kinds=kinds, dry_run=bool(a.get('dry_run', True)), min_severity=a.get('min_severity') or 'medium', hops=int(a.get('hops') or 1), actor=a.get('actor'), override=bool(a.get('override')));action 为 calibrate/calibration 返回 scrub.calibrate(cg, apply=bool(a.get('apply')), override=bool(a.get('override')), actor=a.get('actor'));action 为 sweep/run 返回 scrub.sweep(cg, n=int(a.get('k') or a.get('n') or scrub.DEFAULT_SAMPLE), seed=a.get('seed'), dry_run=bool(a.get('dry_run', True)), hops=int(a.get('hops') or scrub.DEFAULT_HOPS), strategy=a.get('strategy') or 'stratified', apply_calibration=bool(a.get('apply')), actor=a.get('actor'));action 为 history/log 返回 scrub.history(cg, limit=int(a.get('k') or 100))(k 假值回落 100);action=summary 返回 scrub.summary(cg);action=catalog 返回 scrub.catalog();其他 action 抛 ValueError;
1346
1568
  def _scrub_call(cg, a):
1347
1569
  """记忆自净统一入口(抽查 / 联想 / 去污染 / 校准偏差)。
1348
1570
 
@@ -1400,8 +1622,77 @@ def _scrub_call(cg, a):
1400
1622
  raise ValueError(f"scrub 未知 action:{act}")
1401
1623
 
1402
1624
 
1625
+ # ---- action 缺省防呆(2026-09-16)------------------------------------------
1626
+ # 与「op 缺省静默降级」同构的第二类静默失败:op 传了、action 漏了 → 各分支按
1627
+ # 字面量默认 action 执行。若默认恰是**读类**,写意图会被静默吞掉且返回形似正常
1628
+ # 的结果——最典型是文档化契约 `cg(op=review, pid, decision, reason)`(不带
1629
+ # action)实际静默走 review_list,裁决从未落盘(契约与实现漂移)。
1630
+ # 处置:签名明确指向非默认 action → 按签名推导并透出 action_derived/hint_action;
1631
+ # 无签名可依 → 维持默认执行但同样透出标记,让「按默认走」可见而非静默。
1632
+ # 判据用「键存在且非 None」(False / 0 / "" 属显式传入),避免真值判断误吞。
1633
+ _ACTION_SIGS = {
1634
+ "review": (("decision", "decide"), ("node_id", "verify_record")),
1635
+ "recent": (("text", "add"), ("content", "add")),
1636
+ "goal": (("goal", "add"), ("action_hint", "add")),
1637
+ # task:**写意图优先、读意图最后**。name→open 先命中——open(upsert)本身
1638
+ # 兼容 plan/result/change 全字段,故「name+change」同传也不会丢信息;把
1639
+ # change→plan_add 放其后,只在「只给 change」时才是纯追加变更。node_id→get
1640
+ # 置于末位:否则「node_id+change」会被静默当成只读(写意图被吞,
1641
+ # 与 review 的 `pid+decision` 静默走 list 同构)。
1642
+ "task": (("name", "open"), ("task_status", "status"),
1643
+ ("result", "status"), ("change", "plan_add"),
1644
+ ("node_id", "get")),
1645
+ "predict": (("hit", "feedback"), ("predicted_node_id", "feedback"),
1646
+ ("actual_node_id", "feedback")),
1647
+ "session": (("summary", "note"), ("text", "note"), ("content", "note")),
1648
+ "insight": (("statement", "record"),),
1649
+ "whitebox": (("questions", "verify_existing"), ("question", "ask"),
1650
+ ("message", "ask"), ("query", "ask"),
1651
+ ("marker", "verify_encoding"),
1652
+ ("content", "remember"), ("text", "remember")),
1653
+ }
1654
+
1655
+ # op → 分支内缺省 action 字面量。**仅供缺省透出提示,不参与执行**;与各
1656
+ # _xxx_call / 分支里的 `a.get("action") or "<默认>"` 保持同步(同源守卫见
1657
+ # test_action_derive.py)。
1658
+ _ACTION_DEFAULT = {
1659
+ "theory": "check", "link": "ls", "scrub": "sweep", "forget": "forget",
1660
+ "protect": "stats", "identity": "profile", "consistency": "check",
1661
+ "metacognition": "report", "self_state": "snapshot", "predict": "routes",
1662
+ "causal": "path", "evolution": "summary", "sustain": "status",
1663
+ "goal": "list", "task": "list", "recent": "list", "review": "list", "session": "recall",
1664
+ "ingest": "stat", "export": "stat", "maintain": "stat",
1665
+ "consolidate": "promote", "insight": "outlook",
1666
+ "ccg": "compile", "ref": "read", "whitebox": "ping",
1667
+ }
1668
+
1669
+
1670
+ # 生效条件:op == "ingest" 时若 str(a.get("path") or "").strip() 非空,则按 a.get("patterns") 为真值→("dir","patterns")、该 path 小写以 ".jsonl" 结尾→("jsonl","path(*.jsonl)")、否则→("file","path");其他 op 按常量 _ACTION_SIGS.get(op, ()) 的顺序取第一个 key 存在于 a 且 a.get(key) is not None 的项返回 (act, key),无匹配则返回 (None, None);
1671
+ def _action_sig(a, op):
1672
+ """action 缺省时的签名推导 → (action, 依据键);无签名可依 → (None, None)。
1673
+
1674
+ `ingest` 单独处理:其 op 语义是**写**(摄入),缺省 action 却是**读**(stat)
1675
+ ——「传了 path 却静默 stat」会把摄入整件事吞掉。类型按**保守优先**推导:
1676
+ patterns→dir、`*.jsonl`→jsonl、其余 path→file(file 最保守:若实为目录会
1677
+ 报错可见,而不会误把整棵目录树摄进来)。
1678
+ """
1679
+ if op == "ingest":
1680
+ path = str(a.get("path") or "").strip()
1681
+ if path:
1682
+ if a.get("patterns"):
1683
+ return "dir", "patterns"
1684
+ if path.lower().endswith(".jsonl"):
1685
+ return "jsonl", "path(*.jsonl)"
1686
+ return "file", "path"
1687
+ for key, act in _ACTION_SIGS.get(op, ()):
1688
+ if key in a and a.get(key) is not None:
1689
+ return act, key
1690
+ return None, None
1691
+
1692
+
1693
+ # 生效条件:op0=(a.get("op") or "").strip().lower(),op0 为空时按 a.get("content")→"write"、a.get("query") 或 a.get("node_id")→"read"、a.get("intent")→"route"、都无→"read" 推导 op;act0=(args.get("action") or "").strip().lower(),act0 为空时用 _action_sig(args, op) 推导且推导出时写回 args["action"];args["op"]=op 后调 _cg_dispatch,返回 dict 且 op0 为空时补 out["op"]、out["op_derived"]=True 与 hint,act0 为空且 (act_derived 或 op in _ACTION_DEFAULT) 时 setdefault("action", eff)、out["action_derived"]=True 并按是否有 act_derived 写 hint_action。
1403
1694
  def _cg_call(cg, a):
1404
- """认知图唯一入口(外层:op 缺省推导兜底 + 推导透出;分发主体见 _cg_dispatch)。"""
1695
+ """认知图唯一入口(外层:op/action 缺省推导兜底 + 推导透出;主体见 _cg_dispatch)。"""
1405
1696
  op0 = (a.get("op") or "").strip().lower()
1406
1697
  op = op0
1407
1698
  if not op:
@@ -1416,23 +1707,222 @@ def _cg_call(cg, a):
1416
1707
  else:
1417
1708
  op = "read" # 无任何签名可依:维持旧缺省 read
1418
1709
  args = dict(a)
1710
+ act0 = (args.get("action") or "").strip().lower()
1711
+ act_sig = act_derived = None
1712
+ if not act0:
1713
+ act_derived, act_sig = _action_sig(args, op)
1714
+ if act_derived:
1715
+ args["action"] = act_derived # 按签名补 action:避免写意图被默认吞掉
1419
1716
  args["op"] = op
1420
1717
  out = _cg_dispatch(cg, args)
1421
- if isinstance(out, dict) and not op0:
1422
- out["op"] = op
1423
- out["op_derived"] = True
1424
- out["hint"] = ("op 未显式传入,已按参数签名推导(本次按 %s 执行);op 为必填参数,"
1425
- "复杂任务中也请始终显式传 op,避免静默执行错误意图" % op)
1718
+ if isinstance(out, dict):
1719
+ if not op0:
1720
+ out["op"] = op
1721
+ out["op_derived"] = True
1722
+ out["hint"] = ("op 未显式传入,已按参数签名推导(本次按 %s 执行);op 为必填参数,"
1723
+ "复杂任务中也请始终显式传 op,避免静默执行错误意图" % op)
1724
+ if not act0 and (act_derived or op in _ACTION_DEFAULT):
1725
+ eff = act_derived or _ACTION_DEFAULT[op]
1726
+ out.setdefault("action", eff)
1727
+ out["action_derived"] = True
1728
+ if act_derived:
1729
+ out["hint_action"] = (
1730
+ "action 未显式传入,已按参数 %s 推导为 action=%s(旧行为会静默走默认 %s,"
1731
+ "意图被吞);建议显式传 action" % (act_sig, act_derived,
1732
+ _ACTION_DEFAULT.get(op, "?")))
1733
+ else:
1734
+ out["hint_action"] = (
1735
+ "action 未显式传入,本次按 %s 的默认 action=%s 执行;"
1736
+ "若意图是其它 action 请显式传 action" % (op, eff))
1737
+ # 状态摘要协议(**单出口挂载**,控制爆炸半径——不逐 op 改造):
1738
+ # 把「它还成不成立」(验证态/存疑/异常/时效)以状态头透出,并把既有
1739
+ # hint 升级为状态头格式(MDCG_STATUS_HEAD=0 回退旧文本)。挂载失败不阻断读面。
1740
+ try:
1741
+ from . import statushdr
1742
+ statushdr.annotate(cg, out)
1743
+ except Exception as exc: # noqa: BLE001
1744
+ out.setdefault("status_head_error", type(exc).__name__)
1426
1745
  return out
1427
1746
 
1428
1747
 
1748
+ def _status_call(cg, a):
1749
+ """验证态 / 依赖 / 双时间轴 / 履历查询(op=status,**只读**)。
1750
+
1751
+ 真源 `md_cg/trust.py`。三形态(`cg(op="status")` 取摘要,`node_id=` 取单节点):
1752
+ · 给 `node_id` → 单节点全貌(describe + 履历 + 状态头渲染);
1753
+ · 不给 / patrol → 全库摘要(存疑 / 过期 / 未生效 / 悬空四类计数);
1754
+ · `action=ledger` → 读验证态台账(`_trust.jsonl`,可按节点过滤)。
1755
+ """
1756
+ from . import statushdr, trust
1757
+ nid = str(a.get("node_id") or a.get("id") or "").strip()
1758
+ act = str(a.get("action") or "").strip().lower()
1759
+ limit = int(a.get("limit") or 20)
1760
+ if act in ("ledger", "log"):
1761
+ rows = trust.load_ledger(cg, node_id=nid or None, limit=limit)
1762
+ return {"op": "status", "action": "ledger", "node_id": nid or None,
1763
+ "count": len(rows), "rows": rows, "readonly": True}
1764
+ if nid and act not in ("patrol", "summary"):
1765
+ de = trust.describe(cg, nid)
1766
+ de.update({"op": "status", "action": "describe", "readonly": True,
1767
+ "status_head": statushdr.render(cg, nid),
1768
+ "ledger": trust.load_ledger(cg, node_id=nid, limit=limit)})
1769
+ return de
1770
+ rep = trust.patrol(cg, limit=limit)
1771
+ rep.update({"op": "status", "action": act or "summary",
1772
+ "summary": trust.summary(cg), "catalog": trust.catalog(cg.root)})
1773
+ # 摘要形态主动挂全库级状态头(annotate 的 _collect_ids 找不到 node_id 不会自动注入)
1774
+ s = rep.get("summary") or {}
1775
+ bits = []
1776
+ dc = s.get("doubted", 0)
1777
+ ec = s.get("expired", 0)
1778
+ nc = s.get("not_yet", 0)
1779
+ uc = s.get("unverified", 0)
1780
+ vc = s.get("verified", 0)
1781
+ if ec:
1782
+ bits.append(f"{statushdr.MARK_ABNORMAL} 异常({ec})")
1783
+ if dc:
1784
+ bits.append(f"{statushdr.MARK_DOUBTED} 存疑({dc})")
1785
+ if nc:
1786
+ bits.append(f"{statushdr.MARK_MODIFIED} 未生效({nc})")
1787
+ if vc:
1788
+ bits.append(f"{statushdr.MARK_VERIFIED} 已验证({vc})")
1789
+ if uc:
1790
+ bits.append(f"{statushdr.MARK_UNVERIFIED} 未验证({uc})")
1791
+ rep["status_head"] = " · ".join(bits) if bits else statushdr.MARK_UNVERIFIED + " 空"
1792
+ # 冷路径队列状态(2026-09-19 热温冷分层)
1793
+ try:
1794
+ from . import coldverify as _cv
1795
+ q = _cv.get(cg)
1796
+ if q is not None:
1797
+ rep["coldverify"] = q.status()
1798
+ except Exception: # noqa: BLE001
1799
+ pass
1800
+ return rep
1801
+
1802
+
1803
+ # 生效条件:a 的 child/parent/relation/batch/时间五参/ordering/aggregation/offset/limit/expand_nodes 原样透传给 provenance.find_edges(本层不校验、不填默认,合法性单点在库层);始终返回该只读结果字典(含 edges/total/matched/returned/aggregates/time_filter 审计块)。
1804
+ def _edges_call(cg, a):
1805
+ """三元组反查(op=edges,**只读**):按任意端 / 谓词 / 时间反查派生边。
1806
+
1807
+ 三元组术语 `subject / predicate / object` 在本层就是 `child / rel / parent`
1808
+ (即 `derived_from` 派生边)——**不新造第二套参数**:`cg` 工具面是扁平
1809
+ schema,`subject` 已被 `identity`(主体语义)占用,同名异义会把两处口径搅在
1810
+ 一起;且 `relation`/`child`/`parent`/`batch` 已是 `link derive` 的既有谓词面。
1811
+
1812
+ 参数**原样透传、不填默认、不做二次校验**,与 `_read_call` 同一策略:
1813
+ 合法性判定单点在库层(`provenance._ordering_of/_aggregation_of` 与
1814
+ `trust.check_time_args`)。本层若自补默认,缺省轴(边 = `observed`,检索 =
1815
+ `effective`)与枚举集就会两处分叉,日后必然漂移。
1816
+
1817
+ 不暴露 `path`(库层测试用注入点):MCP 面不接受文件系统路径。
1818
+ """
1819
+ from . import provenance as _pv
1820
+ return _pv.find_edges(
1821
+ cg,
1822
+ child=a.get("child"), parent=a.get("parent"),
1823
+ relation=a.get("relation"), batch=a.get("batch"),
1824
+ start_time=a.get("start_time"), end_time=a.get("end_time"),
1825
+ start_operator=a.get("start_operator"), end_operator=a.get("end_operator"),
1826
+ time_axis=a.get("time_axis"),
1827
+ ordering=a.get("ordering"), offset=a.get("offset") or 0,
1828
+ limit=a.get("limit"), aggregation=a.get("aggregation"),
1829
+ expand_nodes=bool(a.get("expand_nodes")))
1830
+
1831
+
1832
+ # 生效条件:始终调 help_text(ALL_TOOLS, query=a.get("query") or a.get("intent"), limit=int(a.get("limit") or a.get("k") or 40)) 并返回其结果。
1833
+ def _help_call(cg, a):
1834
+ """按需披露入口(工具面渐进披露的读取面,见 md_cg/tool_face.py)。
1835
+
1836
+ 常驻上下文只留「一行职责 + 参数短提示」;被投影掉的完整 op/参数语义
1837
+ 在此从真源无损取回——所以瘦身不损失能力,只把「随时付费」换成「按需取」。
1838
+ 只读元信息(工具 schema 本身对客户端可见),故不过角色权限闸。
1839
+ """
1840
+ from .tool_face import help_text
1841
+ return help_text(ALL_TOOLS, query=a.get("query") or a.get("intent"),
1842
+ limit=int(a.get("limit") or a.get("k") or 40))
1843
+
1844
+
1845
+ # 生效条件:当 cg、a 传入时,act=(a.get('action') or '').strip().lower()(空串/None 得空串),若 act 空则 act=_action_sig(a, 'task')[0] or 'list',name=a.get('name') or a.get('task_name') or a.get('task') or '',nid=a.get('node_id') or a.get('task_id') or '',tstat=a.get('task_status') or a.get('new_status') or ''(各假值链回落 ''):act 为 open/add/upsert 时 importance 取 a.get('importance'),非 None 则 float、转换失败置 None,返回 _t.upsert(cg, name or nid, plan=a.get('plan'), status=tstat or None, result=a.get('result'), condition=a.get('condition'), goal=a.get('goal_text') or a.get('goal'), acceptance=a.get('acceptance'), boundary=a.get('boundary'), change=a.get('change'), note=a.get('note'), tags=a.get('tags'), importance=imp, actor=a.get('actor'));act 为 status/set_status 时若 tstat 假值返回 {'ok': False, 'error': '缺 task_status', 'hint': '可选 active|blocked|done|dropped;迁 done 必须同时给 result'},否则返回 _t.set_status(cg, nid or name, tstat, result=a.get('result'), note=a.get('note'), actor=a.get('actor'));act=plan_add 返回 _t.plan_add(cg, nid or name, a.get('change') or a.get('text'), actor=a.get('actor'));act=get 返回 _t.get_task(cg, nid or name);act=find 返回 _t.find_similar(cg, name, k=int(a.get('k') or a.get('limit') or 5))(k/limit 假值链回落 5);act=session 返回 _t.session_tasks(cg, active_limit=int(a.get('active_limit') or 5), done_limit=int(a.get('done_limit') or 5))(各假值回落 5);act 非 list 时返回 {'ok': False, 'error': '未知 task action:%r' % act, 'hint': '可选 open|status|plan_add|get|list|find|session'};act=list 返回 _t.list_tasks(cg, status=tstat or None, limit=a.get('limit'));
1846
+ def _task_call(cg, a):
1847
+ """结构层任务实体(op=task)——跨会话的工程台账。
1848
+
1849
+ action:
1850
+ open / add 登记或更新任务卡(**同名即同卡**;未给字段沿用旧值,不静默清空)
1851
+ status 状态迁移(active|blocked|done|dropped;迁 done 时「结果」必填)
1852
+ plan_add 追加计划变更(执行中发现的偏差与新问题)
1853
+ get 单卡全字段读回
1854
+ list 任务清单(默认;可按 task_status 过滤)
1855
+ find 同族任务提示(**只提示,不自动合并**)
1856
+ session 会话装配面(进行中 + 近期完成)——供上下文召回
1857
+
1858
+ :param name: 任务名(身份判据:同名即同任务,slug 归一后落节点 id)
1859
+ """
1860
+ from . import tasks as _t
1861
+ act = (a.get("action") or "").strip().lower()
1862
+ if not act:
1863
+ act = _action_sig(a, "task")[0] or "list"
1864
+ name = a.get("name") or a.get("task_name") or a.get("task") or ""
1865
+ nid = a.get("node_id") or a.get("task_id") or ""
1866
+ tstat = a.get("task_status") or a.get("new_status") or ""
1867
+
1868
+ if act in ("open", "add", "upsert"):
1869
+ imp = a.get("importance")
1870
+ try:
1871
+ imp = float(imp) if imp is not None else None
1872
+ except (TypeError, ValueError):
1873
+ imp = None
1874
+ return _t.upsert(cg, name or nid,
1875
+ plan=a.get("plan"), status=tstat or None,
1876
+ result=a.get("result"), condition=a.get("condition"),
1877
+ goal=a.get("goal_text") or a.get("goal"),
1878
+ acceptance=a.get("acceptance"), boundary=a.get("boundary"),
1879
+ change=a.get("change"), note=a.get("note"),
1880
+ tags=a.get("tags"), importance=imp, actor=a.get("actor"))
1881
+
1882
+ if act in ("status", "set_status"):
1883
+ if not tstat:
1884
+ return {"ok": False, "error": "缺 task_status",
1885
+ "hint": "可选 active|blocked|done|dropped;迁 done 必须同时给 result"}
1886
+ return _t.set_status(cg, nid or name, tstat, result=a.get("result"),
1887
+ note=a.get("note"), actor=a.get("actor"))
1888
+
1889
+ if act == "plan_add":
1890
+ return _t.plan_add(cg, nid or name, a.get("change") or a.get("text"),
1891
+ actor=a.get("actor"))
1892
+
1893
+ if act == "get":
1894
+ return _t.get_task(cg, nid or name)
1895
+
1896
+ if act == "find":
1897
+ return _t.find_similar(cg, name, k=int(a.get("k") or a.get("limit") or 5))
1898
+
1899
+ if act == "session":
1900
+ return _t.session_tasks(cg, active_limit=int(a.get("active_limit") or 5),
1901
+ done_limit=int(a.get("done_limit") or 5))
1902
+
1903
+ if act != "list":
1904
+ return {"ok": False, "error": "未知 task action:%r" % act,
1905
+ "hint": "可选 open|status|plan_add|get|list|find|session"}
1906
+
1907
+ return _t.list_tasks(cg, status=tstat or None, limit=a.get("limit"))
1908
+
1909
+
1910
+ # 生效条件:op 取 a.get("op") 转 str 去空白并 lower;缺失时按参数签名回推(含 content→write / query|node_id→read / intent→route;无签名可推时维持 read),op=="help" 在角色闸门之前直通 _help_call;其余 op 先经 cg.principal.require_op(op)(越权即 AccessDenied)再按 alias→内联名→cli 表分派;未识别 op 抛 ValueError;
1429
1911
  def _cg_dispatch(cg, a):
1430
1912
  """认知图唯一入口的 op 分发主体。"""
1431
1913
  op = (a.get("op") or "read").strip().lower()
1914
+ if op == "help":
1915
+ return _help_call(cg, a)
1432
1916
  _p = getattr(cg, "principal", None)
1433
1917
  if _p is not None and hasattr(_p, "require_op"):
1434
1918
  _p.require_op(op) # 角色作用域闸门:越权即 AccessDenied
1435
1919
 
1920
+ if op == "status":
1921
+ return _status_call(cg, a)
1922
+
1923
+ if op == "edges":
1924
+ return _edges_call(cg, a)
1925
+
1436
1926
  if op == "theory":
1437
1927
  from . import theory as _th
1438
1928
  act = (a.get("action") or "check").strip().lower()
@@ -1561,12 +2051,15 @@ def _cg_dispatch(cg, a):
1561
2051
 
1562
2052
  if op == "info":
1563
2053
  from . import audit
2054
+ from . import nodefile
1564
2055
  from . import theory as _th
1565
2056
  from . import links as _lk
1566
2057
  h = cg.health_os()
1567
2058
  h.update({"surface": SURFACE,
1568
2059
  "tools": [t["name"] for t in tools_for_surface()],
1569
- "audit_kinds": audit.kinds(), "whoami": cg.whoami()})
2060
+ "audit_kinds": audit.kinds(), "whoami": cg.whoami(),
2061
+ # 裁定 B:CCG 六要素的契约角色(术语真源在 nodefile,此处只透出)
2062
+ "ccg_contract": dict(nodefile.CCG_CONTRACT_ROLES)})
1570
2063
  # 能力外置可观测:本进程实际注入了哪些外部验证器(含失败原因)
1571
2064
  h["external_verifiers"] = audit.load_external_verifiers()
1572
2065
  h["theory"] = _th.check()
@@ -1576,7 +2069,8 @@ def _cg_dispatch(cg, a):
1576
2069
  if op == "route":
1577
2070
  intent = a.get("intent") or a.get("query") or ""
1578
2071
  res, meta = cg.search(intent, k=int(a.get("k") or 10),
1579
- context=a.get("context"), record=False)
2072
+ context=a.get("context"), record=False,
2073
+ view=a.get("view"))
1580
2074
  knowledge, caps = [], []
1581
2075
  for n, s, q in res:
1582
2076
  fm = n.get("frontmatter") or {}
@@ -1594,8 +2088,20 @@ def _cg_dispatch(cg, a):
1594
2088
  "note": "认知图只给知识与建议能力名,不执行;由调用方决定"}
1595
2089
 
1596
2090
  if op == "read":
2091
+ # 时间算子(阶段二 4.1):**原样透传**五个时间入参——本层不做校验也不填
2092
+ # 默认值,合法性判定单点在库层 `trust.check_time_args`(默认轴 effective
2093
+ # 亦由库层 `time_axis_of` 决定)。本层若自填默认值,两处口径就会分叉。
2094
+ _tkw = {"start_time": a.get("start_time"), "end_time": a.get("end_time"),
2095
+ "start_operator": a.get("start_operator"),
2096
+ "end_operator": a.get("end_operator"),
2097
+ "time_axis": a.get("time_axis"),
2098
+ # 角色化读取视图(第四阶段 6.1):原样透传,合法性判定单点在
2099
+ # roleviews.ROLE_VIEWS(非法 view 库层 ValueError),与时间算子
2100
+ # 同一透传纪律——本层不做校验也不填默认值。
2101
+ "view": a.get("view")}
1597
2102
  if a.get("node_id"):
1598
- return _node_view(cg.get(a["node_id"]))
2103
+ return _node_view(cg.get(a["node_id"]),
2104
+ offset=int(a.get("offset") or 0))
1599
2105
  q = a.get("query") or a.get("intent") or ""
1600
2106
  if a.get("budget_tokens"):
1601
2107
  return cg.recall(q, budget_tokens=int(a["budget_tokens"]),
@@ -1603,121 +2109,25 @@ def _cg_dispatch(cg, a):
1603
2109
  goal_text=a.get("goal"),
1604
2110
  include_recent=bool(a.get("include_recent")),
1605
2111
  recent_limit=int(a.get("limit") or 10),
1606
- session=a.get("session"))
2112
+ session=a.get("session"),
2113
+ validity=a.get("validity"), **_tkw)
1607
2114
  from . import refindex
1608
2115
  res, meta = cg.search(q, layer=a.get("layer"), k=int(a.get("k") or 20),
1609
2116
  context=a.get("context"),
1610
- session=a.get("session"))
2117
+ session=a.get("session"),
2118
+ validity=a.get("validity"), **_tkw)
1611
2119
  return {"meta": meta, "results": [
1612
2120
  {"node": _node_view(n), "score": s, "state": q2.get("state"),
1613
2121
  "reason": q2.get("reason"), **refindex.ref_fields(n)}
1614
2122
  for n, s, q2 in res]}
1615
2123
 
1616
2124
  if op == "write":
1617
- from . import audit
1618
- verdict = audit.audit(
1619
- (a.get("content_kind") or "").strip(),
1620
- {"content": a.get("content", ""), "action": a.get("action"),
1621
- "sensitivity": a.get("sensitivity"),
1622
- "topic": a.get("query") or a.get("intent")},
1623
- {"cg": cg, "principal": getattr(cg, "principal", None)})
1624
- nid = a.get("node_id") or ("mem_" + str(int(__import__("time").time() * 1000)))
1625
- st = verdict["state"]
1626
- if st == audit.ACCEPT:
1627
- # 节点间自动冲突检测(三级决策:情绪→反思→递归反思):
1628
- # 写入前与既有条件/纪律校验;不通过则不落盘(可进审核队列)。
1629
- # 与 mdcg_remember 同源、默认开启——「信息的修改必须先通过校验」。
1630
- cvd = None
1631
- if bool(a.get("consistency", True)):
1632
- oc = (a.get("on_conflict") or "defer").strip().lower()
1633
- cvd = cg.check_consistency(
1634
- a.get("content", ""),
1635
- layer=a.get("layer") or ("contextual" if a.get("gated")
1636
- else "knowledge"),
1637
- condition_space=a.get("condition_space"),
1638
- non_applicable_conditions=a.get("non_applicable_conditions"),
1639
- tags=a.get("tags"), exclude=nid, auto_flywheel=True)
1640
- v = cvd.get("verdict")
1641
- blocked = ((v == "REJECT" and oc == "reject")
1642
- or (v in ("REJECT", "BLINDSPOT") and oc == "defer"))
1643
- if blocked:
1644
- if oc == "reject":
1645
- return {"ok": False, "id": nid, "committed": False,
1646
- "moved_to": "conflict_rejected",
1647
- "consistency": cvd, "verdict": verdict}
1648
- pid = cg.propose(nid, a.get("content", ""),
1649
- layer=a.get("layer") or "knowledge",
1650
- tags=a.get("tags"),
1651
- condition_space=a.get("condition_space"),
1652
- **_proposal_extras(a, verdict))
1653
- return {"ok": False, "id": nid, "pid": pid, "committed": False,
1654
- "moved_to": "review_queue", "consistency": cvd,
1655
- "verdict": verdict,
1656
- "hint": "这是冲突闸门的正常行为:本次写入与既有条件/纪律冲突"
1657
- "(on_conflict=defer),已转入审核队列待裁决——"
1658
- "不是工具故障,重试同样结果;"
1659
- "落盘须由设计者权限(can_admin)裁决,转告使用者:"
1660
- "python scripts/review_cli.py list 后 accept/reject,"
1661
- "或 cg(op=review, pid=<pid>, decision=accept|reject|"
1662
- "edit|merge, reason=<理由>)"}
1663
- if a.get("gated"):
1664
- hint = a.get("importance_hint")
1665
- if hint is None and a.get("importance") is not None:
1666
- hint = float(a["importance"])
1667
- res = cg.remember_gated(
1668
- nid, a.get("content", ""), layer=a.get("layer") or "contextual",
1669
- role=a.get("role"), tags=a.get("tags"),
1670
- condition_space=a.get("condition_space"),
1671
- verification_basis=(a.get("verification_basis")
1672
- or verdict.get("basis")),
1673
- non_applicable_conditions=a.get("non_applicable_conditions"),
1674
- importance_hint=hint, override=bool(a.get("override")),
1675
- consistency=False,
1676
- derived_from=_split_ids(a.get("derived_from")),
1677
- relation=a.get("relation"))
1678
- v = res.get("verdict")
1679
- committed = v == "ACCEPT"
1680
- out = {"ok": committed, "id": nid, "committed": committed,
1681
- "gate": res, "verdict": verdict}
1682
- if cvd is not None:
1683
- out["consistency"] = cvd
1684
- if v == "MERGE":
1685
- out["moved_to"] = "merged_into:" + str(res.get("merged_into"))
1686
- elif v in ("DROP", "DEFER"):
1687
- out["moved_to"] = v.lower()
1688
- return out
1689
- cg.add(nid, a.get("content", ""), layer=a.get("layer") or "knowledge",
1690
- tags=a.get("tags"), condition_space=a.get("condition_space"),
1691
- importance=float(a.get("importance", 0.5)),
1692
- verification_basis=a.get("verification_basis") or verdict.get("basis"),
1693
- non_applicable_conditions=a.get("non_applicable_conditions"),
1694
- override=bool(a.get("override")), consistency=False,
1695
- derived_from=_split_ids(a.get("derived_from")),
1696
- relation=a.get("relation"))
1697
- out = {"ok": True, "id": nid, "committed": True, "verdict": verdict}
1698
- if cvd is not None:
1699
- out["consistency"] = cvd
1700
- return out
1701
- if st == audit.REJECT:
1702
- rid = cg.add_rejected((a.get("content") or "")[:200], verdict["evidence"],
1703
- verification_basis=verdict.get("basis") or "test",
1704
- tags=a.get("tags"))
1705
- return {"ok": False, "id": rid, "committed": False,
1706
- "moved_to": "rejected", "verdict": verdict,
1707
- "hint": "这是审核闸门的正常行为:内容未过内容政策审核(REJECT),"
1708
- "已记入负记忆——不是工具故障,重试同样结果;"
1709
- "拒绝依据见 verdict.evidence"}
1710
- pid = cg.propose(nid, a.get("content", ""), layer=a.get("layer") or "knowledge",
1711
- tags=a.get("tags"), condition_space=a.get("condition_space"),
1712
- **_proposal_extras(a, verdict))
1713
- return {"ok": True, "id": nid, "pid": pid, "committed": False,
1714
- "moved_to": "review_queue", "verdict": verdict,
1715
- "hint": "这是校验闸门的正常行为(verdict=%s):内容未达 ACCEPT,"
1716
- "已入审核队列——不需要重试;落盘须由设计者权限(can_admin)"
1717
- "对提案 pid 裁决(agent 端无裁决权是设计),转告使用者:"
1718
- "python scripts/review_cli.py list 后 accept/reject,"
1719
- "或 cg(op=review, pid=<pid>, decision=accept|reject|"
1720
- "edit|merge, reason=<理由>)" % verdict.get("state")}
2125
+ # 写入路径拦截器链(Pi 钩子化移植,writepipe.py):六道闸以扩展
2126
+ # 形态注册于 writepipe.default_pipeline(),次序/启停不再硬编码于
2127
+ # 本文件;新增闸门 = register_before 一行,即插即拔。
2128
+ # 角色/层权限校验在 MdCGSecure.add 库层,结构上不可被拦截器绕过。
2129
+ from .writepipe import default_pipeline
2130
+ return default_pipeline().execute(cg, a)
1721
2131
 
1722
2132
  if op == "goal":
1723
2133
  act = (a.get("action") or "list").strip().lower()
@@ -1738,6 +2148,9 @@ def _cg_dispatch(cg, a):
1738
2148
  limit=int(a.get("limit") or 20)),
1739
2149
  "active": cg.active_goals(limit=int(a.get("limit") or 5))}
1740
2150
 
2151
+ if op == "task":
2152
+ return _task_call(cg, a)
2153
+
1741
2154
  if op == "recent":
1742
2155
  act = (a.get("action") or "list").strip().lower()
1743
2156
  if act in ("add", "append", "remember"):
@@ -1758,6 +2171,10 @@ def _cg_dispatch(cg, a):
1758
2171
  act = (a.get("action") or "list").strip().lower()
1759
2172
  if act == "list":
1760
2173
  return {"pending": cg.review_list()}
2174
+ if act == "stats":
2175
+ # 裁决动作分布(含 noop):无此出口则「已评估、判定无需改动」这类
2176
+ # 裁决只在 jsonl 里躺着,治理面看不到——与「静默忽略」等价。
2177
+ return cg.review_stats()
1761
2178
  if act == "rounds":
1762
2179
  pid = a.get("pid", "")
1763
2180
  return {"pid": pid, "rounds": cg.review_rounds(pid)}
@@ -1898,9 +2315,184 @@ def _cg_dispatch(cg, a):
1898
2315
  if op == "insight":
1899
2316
  return _insight_call(cg, a)
1900
2317
 
2318
+ if op == "ccg":
2319
+ return _ccg_call(cg, a)
2320
+
1901
2321
  raise ValueError(f"cg 未知 op:{op}")
1902
2322
 
1903
2323
 
2324
+ # 生效条件:由 _cg_dispatch 在 op=="ccg" 时进入;action 取 a.get("action") 缺省 "compile";compile 只产出候选(落 _ccgc_pending/ 待复核、不当场写库),link 才准入落库,attest/recalibrate 须编外验证方且 verifier != compiled_by(E041/E042/E043 机械把关);
2325
+ def _ccg_call(cg, a):
2326
+ """CCG 六要素编译器(op=ccg):对话记录 → 六要素候选 → **编外复核** → 落库。
2327
+
2328
+ 定位:**记忆可靠性闸**——不是又一个写入通道,而是写入前「条件是否成立」的检查。
2329
+ 裁定 A(LLM 不得自己验证自己)由 ccgc 的 E041 **机械执行**,不依赖 prompt 自觉。
2330
+
2331
+ action(缺省 compile):
2332
+ compile 对话记录 → 六要素候选(五环编译);候选+签章槽落
2333
+ `_ccgc_pending/<node_id>.json`(**不当场写库**)
2334
+ review 派发编外复核单元:**蜂巢 reflect/verify 优先** → 不可用则提示配置
2335
+ → 显式 allow_degrade 才降级 harness 端子代理;拿到裁决即自动签章
2336
+ attest 外部裁决签章(verifier 须 != compiled_by;E041/E042 机械把关)
2337
+ link 读回候选+签章 → 准入校验 → apply=True 落库(成功后清 pending)
2338
+ recalibrate 四槽修正(须编外验证方;E043 只放开 condition_space 四槽)
2339
+ units 复核通道体检/计划(hive → 提示配置 → 子代理 三级链)
2340
+ catalog 引擎自描述(错误码/契约角色/动作清单)
2341
+
2342
+ 入参集中在 `ccg` 对象里(与 `redteam`/`verify` 同风格)——避免与既有扁平参数
2343
+ 重名(如 `verdict` 在 verify 面是 confirmed|weakened|falsified,语义不同)。
2344
+
2345
+ 返回语义:`ok` 表示**本次请求本身**是否成立;`verdict` 是单元给出的裁决
2346
+ (未复核为 None);`passed` = 裁决为 ACCEPT。三者分开,避免「派发成功」
2347
+ 被误读成「复核通过」。
2348
+
2349
+ 权限:编译器属写路径 → 记录/设计者角色可用(见 tokens.ALL_OPS 的 ccg 条目)。
2350
+ attest/link 的准入**不靠 admin 闸**,而靠签章机械闸(E040/E041/E042)——理由:
2351
+ 默认部署(未设 MDCG_CAN_ADMIN)下再加 admin 会让编译器对使用者不可见
2352
+ (「权限到位但通路不可见」是既有教训),而裁定 A 的保证本就在机械闸里。
2353
+ """
2354
+ from . import ccgc
2355
+ from . import units as _units
2356
+
2357
+ o = a.get("ccg") or {}
2358
+ if not isinstance(o, dict):
2359
+ return {"ok": False, "op": "ccg",
2360
+ "error": "ccg 参数须为对象:{action, node_id, dialog, ...}"}
2361
+ act = str(o.get("action") or a.get("action") or "compile").strip().lower()
2362
+ node_id = str(o.get("node_id") or a.get("node_id") or "").strip()
2363
+ actor = str(getattr(getattr(cg, "principal", None), "actor", "") or o.get("actor") or "agent")
2364
+ jobs = str(o.get("jobs") or "")
2365
+
2366
+ if act == "catalog":
2367
+ return {"ok": True, "op": "ccg", "action": "catalog",
2368
+ "actions": ["compile", "review", "attest", "link", "recalibrate",
2369
+ "units", "catalog"],
2370
+ "states": list(ccgc.STATES), "contract_roles": list(ccgc.CONTRACT_ROLES),
2371
+ "errors": dict(ccgc.E_CODES),
2372
+ "rule": "裁定 A:验证方标识不得等于编译执行者(E041);缺签章不写库(E040);"
2373
+ "未通过不写库(E042)"}
2374
+
2375
+ if act == "units":
2376
+ if o.get("doctor"):
2377
+ return {"ok": True, "op": "ccg", "action": "units", **_units.doctor(jobs)}
2378
+ p = _units.plan(jobs=jobs, model=o.get("model") or "",
2379
+ allow_degrade=bool(o.get("allow_degrade")),
2380
+ channel=o.get("channel") or "")
2381
+ return {"ok": True, "op": "ccg", "action": "units", **p}
2382
+
2383
+ if act == "compile":
2384
+ dialog = str(o.get("dialog") or o.get("content") or a.get("content") or "")
2385
+ if not dialog.strip():
2386
+ return {"ok": False, "op": "ccg", "action": "compile",
2387
+ "error": "缺对话记录:传 ccg.dialog(或顶层 content)"}
2388
+ if not node_id:
2389
+ return {"ok": False, "op": "ccg", "action": "compile",
2390
+ "error": "缺 node_id:六要素候选须指明目标节点"}
2391
+ res = ccgc.compile_dialog(dialog, node_id, actor,
2392
+ marks=o.get("marks"), slots=o.get("slots"),
2393
+ strict_spans=bool(o.get("strict_spans", True)), cg=cg)
2394
+ saved = ccgc.save_pending(cg, res)
2395
+ return {"ok": bool(res.success), "op": "ccg", "action": "compile",
2396
+ "compiled": ccgc.asdict(res), "pending": saved,
2397
+ "actor": actor, "compiled_by": actor,
2398
+ "hint": ("候选已落 pending;下一步 cg(op=ccg, action=review, "
2399
+ "ccg={node_id, blocking:true}) 交编外单元复核"
2400
+ "——编译者不得自证(E041)" if res.success else
2401
+ "编译未通过:" + (res.errors[0] if res.errors else "未知错误"))}
2402
+
2403
+ if act == "review":
2404
+ got = ccgc.load_pending(cg, node_id)
2405
+ if not got.get("ok") or not got.get("hash_ok"):
2406
+ return {"ok": False, "op": "ccg", "action": "review",
2407
+ "error": got.get("error") or "pending 产物与编译时不一致(hash 不符)",
2408
+ "hint": "先 cg(op=ccg, action=compile, ccg={node_id, dialog}) 生成候选"}
2409
+ compiled = got["compiled"]
2410
+ role = str(o.get("role") or _units.REFLECT).strip().lower()
2411
+ prompt = _units.prompt_for(role, compiled, dialog=str(o.get("dialog") or ""))
2412
+ r = _units.review(prompt=prompt, role=role, node_id=node_id, jobs=jobs,
2413
+ model=o.get("model") or "",
2414
+ timeout_s=int(o.get("timeout_s") or _units.DEFAULT_TIMEOUT_S),
2415
+ allow_degrade=bool(o.get("allow_degrade")),
2416
+ channel=o.get("channel") or "",
2417
+ wait_s=float(o.get("wait_s") or _units.DEFAULT_TIMEOUT_S),
2418
+ blocking=bool(o.get("blocking")), cg=cg, actor=actor,
2419
+ autostart=bool(o.get("autostart")))
2420
+ unit = r.get("unit")
2421
+ out = {"ok": True, "op": "ccg", "action": "review", "state": r["state"],
2422
+ "role": role, "node_id": node_id, "job_id": r.get("job_id"),
2423
+ "transport": r.get("transport"), "verdict": (unit or {}).get("verdict"),
2424
+ "passed": bool((unit or {}).get("verdict") == ccgc.ACCEPT),
2425
+ "unit": unit, "channel": r.get("channel") or "",
2426
+ "blocking": bool(o.get("blocking")), "hint": r.get("hint") or ""}
2427
+ if r["state"] != _units.HIVE:
2428
+ out["ok"] = False
2429
+ out["prompt"] = r.get("prompt") # 降级/配置路径:把复核请求包交回调用方
2430
+ return out
2431
+ if unit: # 已获编外裁决 → 自动签章
2432
+ args = r.get("attest") or {}
2433
+ at = ccgc.attest(node_id, args.get("verdict") or ccgc.DEFER,
2434
+ args.get("verifier") or "", compiled.actor or actor,
2435
+ slot_corrections=args.get("slot_corrections"),
2436
+ evidence=args.get("evidence") or "", cg=cg)
2437
+ out["attest"] = ccgc.asdict(at)
2438
+ out["pending"] = ccgc.save_pending(cg, compiled, at)
2439
+ out["hint"] = ("已签章(%s);下一步 cg(op=ccg, action=link, "
2440
+ "ccg={node_id, apply:true}) 落库"
2441
+ % (args.get("verifier") or "-") if at.ok else
2442
+ "签章未通过:" + (at.error or "DEFER/REJECT 不构成准入"))
2443
+ return out
2444
+
2445
+ if act == "attest":
2446
+ got = ccgc.load_pending(cg, node_id)
2447
+ if not got.get("ok") or not got.get("hash_ok"):
2448
+ return {"ok": False, "op": "ccg", "action": "attest",
2449
+ "error": got.get("error") or "pending 产物与编译时不一致(hash 不符)",
2450
+ "hint": "先 cg(op=ccg, action=compile, ...) 生成候选"}
2451
+ compiled = got["compiled"]
2452
+ at = ccgc.attest(node_id, str(o.get("verdict") or ""),
2453
+ str(o.get("verifier") or ""),
2454
+ str(o.get("compiled_by") or compiled.actor or actor),
2455
+ slot_corrections=o.get("slot_corrections"),
2456
+ evidence=str(o.get("evidence") or ""), cg=cg)
2457
+ return {"ok": bool(at.ok), "op": "ccg", "action": "attest",
2458
+ "attest": ccgc.asdict(at),
2459
+ "pending": ccgc.save_pending(cg, compiled, at),
2460
+ "hint": ("签章通过;下一步 cg(op=ccg, action=link, ccg={node_id, apply:true})"
2461
+ if at.ok else "签章未通过:" + (at.error or "DEFER/REJECT 不构成准入"))}
2462
+
2463
+ if act == "link":
2464
+ apply = bool(o.get("apply"))
2465
+ res = ccgc.link_pending(cg, node_id, apply=apply, actor=actor,
2466
+ basis=str(o.get("basis") or "ccgc link"))
2467
+ return {"ok": bool(res.ok), "op": "ccg", "action": "link", "apply": apply,
2468
+ "link": ccgc.asdict(res),
2469
+ "hint": ("已落库(entry_id=%s,written=%s)" % (res.entry_id, res.written)
2470
+ if res.ok and apply else
2471
+ ("准入通过(dry_run 未写库);apply=true 才落库" if res.ok else
2472
+ ";".join(res.errors or []) or "准入未通过"))}
2473
+
2474
+ if act == "recalibrate":
2475
+ corr = o.get("slot_corrections") or o.get("corrections") or {}
2476
+ verifier = str(o.get("verifier") or "").strip()
2477
+ compiled_by = str(o.get("compiled_by") or "").strip()
2478
+ if not verifier or verifier == compiled_by:
2479
+ return {"ok": False, "op": "ccg", "action": "recalibrate",
2480
+ "error": "E041 自证拒绝:recalibrate 须由编外验证方给出"
2481
+ "(verifier 必填且 != compiled_by)"}
2482
+ res = ccgc.recalibrate(node_id, corr, verifier, compiled_by,
2483
+ evidence=str(o.get("evidence") or ""), cg=cg,
2484
+ apply=bool(o.get("apply")))
2485
+ return {"ok": bool(res.ok), "op": "ccg", "action": "recalibrate",
2486
+ "recalibrate": ccgc.asdict(res),
2487
+ "hint": (";".join(res.errors or []) or "修正已应用(apply=true)" if res.ok
2488
+ else ";".join(res.errors or []) or "修正未通过")}
2489
+
2490
+ return {"ok": False, "op": "ccg", "action": act,
2491
+ "error": "ccg 未知 action:%s(可选 compile|review|attest|link|recalibrate|"
2492
+ "units|catalog)" % act}
2493
+
2494
+
2495
+ # 生效条件:act=(a.get("action") or "recall").strip().lower();act=="note" 时若 principal 非 None 且其 can_write 为假先 require_admin("session_note"),再调 cg.session_note(summary/text/content 之一或 "",importance=float(0.6 if a.get("importance") is None else a.get("importance")) 等);act=="recall" 时调 cg.session_recall(...);act=="compact" 时若 a.get("note") 为真且 principal 非 None 且无写权先 require_admin("session_compact"),再调 cg.session_compact(...);其余 act 抛 ValueError。
1904
2496
  def _session_call(cg, a):
1905
2497
  """会话三件套(P0):note 写要点 / recall 续接 / compact 压摘要。
1906
2498
 
@@ -1939,6 +2531,7 @@ def _session_call(cg, a):
1939
2531
  raise ValueError(f"session 未知 action:{act}(允许 note/recall/compact)")
1940
2532
 
1941
2533
 
2534
+ # 生效条件:flag=a.get("prune", True),flag 为字符串时重算为 flag.strip().lower() not in ("false","0","no","off","");若 not flag 或 stats.get("truncated") 为真则返回 None;否则返回 refindex.prune_orphans(cg, kind=kind, root=root, items=items, dry_run=bool(a.get("prune_dry_run"))).
1942
2535
  def _prune_after_index(cg, a, *, kind, root, items, stats):
1943
2536
  """索引后的**节点级对账**:清退「同 root + 同 path 的过期代」。
1944
2537
 
@@ -1961,6 +2554,7 @@ def _prune_after_index(cg, a, *, kind, root, items, stats):
1961
2554
  dry_run=bool(a.get("prune_dry_run")))
1962
2555
 
1963
2556
 
2557
+ # 生效条件:sd=list(stats.get("skipped_dirs") or []),返回 {"skipped_dirs": sd[:limit], "skipped_dirs_count": len(sd), "skip_dirs": list(stats.get("skip_dirs") or [])},且仅当 len(sd)>limit 时追加 skipped_dirs_note。
1964
2558
  def _skip_dirs_report(stats, limit=20):
1965
2559
  """把「本次被 skip_dirs 排掉的目录」压成可审计字段。
1966
2560
 
@@ -1975,6 +2569,7 @@ def _skip_dirs_report(stats, limit=20):
1975
2569
  return out
1976
2570
 
1977
2571
 
2572
+ # 生效条件:act=(a.get("action") or "stat").strip().lower();act 属 ("file","dir","jsonl") 且 principal 非 None 且其 can_write 为假时先 require_admin(f"ingest_{act}"),随后以 action=act 及各透传参数调 sources.run 并返回。
1978
2573
  def _ingest_call(cg, a):
1979
2574
  """文件摄取分派(P0):file / dir / jsonl / stat。
1980
2575
 
@@ -1995,6 +2590,7 @@ def _ingest_call(cg, a):
1995
2590
  max_events=a.get("max_events"))
1996
2591
 
1997
2592
 
2593
+ # 生效条件:act=(a.get("action") or "stat").strip().lower();principal 非 None 时一律先 require_admin(f"export_{act}"),随后以 include_content=True if a.get("include_content") is None else bool(a.get("include_content")) 等参数调 _ex.run 并返回。
1998
2594
  def _export_call(cg, a):
1999
2595
  """全库导出(P0):graph / nodes / slice / stat。
2000
2596
 
@@ -2013,8 +2609,9 @@ def _export_call(cg, a):
2013
2609
  include_content=True if inc is None else bool(inc))
2014
2610
 
2015
2611
 
2612
+ # 生效条件:act=(a.get("action") or "stat").strip().lower(),apply=bool(a.get("apply")),mode=str(a.get("mode") or "").strip().lower();principal 非 None 时对 act=="rollback"、act in ("importance","separate") 且 apply、act=="longterm" 且 apply、act=="prefeed" 且 a.get("write") 真且无写权、act in ("backfill","cap","exempt") 且 apply、act in ("backfill_rollback","cap_rollback","exempt_rollback")、act=="vision_evidence" 且 apply、act=="vision_evidence_rollback"、act=="refine" 且 apply 分别 require_admin;随后 act=="longterm" 且 mode in ("list","ls","show","read") 时仅以 action/mode/limit/snapshot_id 调 cg.maintain,否则以全部参数调 cg.maintain。
2016
2613
  def _maintain_call(cg, a):
2017
- """记忆维护(P1):importance / longterm / prefeed / separate / rollback / stat。
2614
+ """记忆维护(P1):importance / longterm / prefeed / separate / rollback / stat / propagate。
2018
2615
 
2019
2616
  权限**按 action 分档**(比整 op 收窄更贴合语义):
2020
2617
  · 只读(stat / history / longterm mode=list|show)→ 不额外拦截;
@@ -2029,6 +2626,19 @@ def _maintain_call(cg, a):
2029
2626
  apply = bool(a.get("apply"))
2030
2627
  mode = str(a.get("mode") or "").strip().lower()
2031
2628
  actor = getattr(principal, "actor", None) if principal is not None else None
2629
+ # P4 可验证记忆单元(2026-09-19):**多跳**失效传播巡检(写路径只做一跳同步,
2630
+ # 保写入延迟恒定——多跳的重算成本交给巡检面)。
2631
+ # · 预演(默认 apply=False)→ 只出可达集报表,放行写层角色先看影响面;
2632
+ # · apply=True → 直接改写下游验证态(同步写盘)→ 管理操作。
2633
+ if act == "propagate":
2634
+ from . import trust as _trust
2635
+ if apply and principal is not None:
2636
+ principal.require_admin("maintain_propagate")
2637
+ rep = _trust.propagate(cg, apply=apply,
2638
+ max_nodes=int(a.get("max_nodes") or 500),
2639
+ actor=actor or "patrol")
2640
+ rep.update({"op": "maintain", "action": "propagate"})
2641
+ return rep
2032
2642
  if principal is not None and act == "rollback":
2033
2643
  principal.require_admin("maintain_rollback")
2034
2644
  if principal is not None and act in ("importance", "separate") and apply:
@@ -2077,6 +2687,7 @@ def _maintain_call(cg, a):
2077
2687
  verdicts=a.get("verdicts"), reason=a.get("reason"))
2078
2688
 
2079
2689
 
2690
+ # 生效条件:act=(a.get("action") or "promote").strip().lower();principal 非 None 时一律先 require_admin(f"consolidate_{act}");imp=a.get("min_importance"),为 None 时回落为 a.get("importance"),随后以 imp 等参数调 cg.consolidate_run 并返回。
2080
2691
  def _consolidate_call(cg, a):
2081
2692
  """离线固化(P1):promote / promote_rollback / promote_history。
2082
2693
 
@@ -2101,15 +2712,20 @@ def _consolidate_call(cg, a):
2101
2712
  actor=getattr(principal, "actor", None) if principal is not None else None)
2102
2713
 
2103
2714
 
2715
+ # 生效条件:act=(a.get("action") or "outlook").strip().lower(),apply=bool(a.get("apply"));principal 非 None 时 can_write=bool(principal.can_write),对 act in ("record","verify") 且无写权、act in ("fork","branch_rewrite","branch_merge") 且无写权、act in ("reconstruct","learn") 且 apply、act=="branch_discard" 分别 require_admin;随后以 action=act 等参数调 cg.insight 并返回。
2104
2716
  def _insight_call(cg, a):
2105
2717
  """洞察(P2):window / record / verify / list / report / reconstruct / learn /
2106
- outlook / catalog。
2718
+ outlook / catalog / fork / branch_rewrite / branch_search / branch_merge /
2719
+ branch_discard / branches / tickets。
2107
2720
 
2108
2721
  权限**按 action 分档**(与 maintain 同构,比整 op 收窄更贴合语义):
2109
- · 只读(window / list / report / reconstruct 预演 / outlook / catalog)
2110
- → 不额外拦截;
2111
- · 条件层记账(record / verify)→ 需 can_write;
2112
- · 落库(reconstruct apply / learn apply)→ 批量改写,require_admin。
2722
+ · 只读(window / list / report / reconstruct 预演 / outlook / catalog /
2723
+ branch_search / branches / fork 预演 / tickets 预演)→ 不额外拦截;
2724
+ · 条件层记账(record / verify)与分支写操作(fork / branch_rewrite /
2725
+ branch_merge)→ 需 can_write;
2726
+ · 落库(reconstruct apply / learn apply / tickets apply 批量建卡)与
2727
+ 分支弃置(branch_discard,改变库可见性)→ require_admin
2728
+ (discard 库层还有第二道闸)。
2113
2729
  这样 output 角色能读洞察但不能记;reflect 能重构与学习预演;批量落库须请示。
2114
2730
  """
2115
2731
  act = (a.get("action") or "outlook").strip().lower()
@@ -2119,23 +2735,34 @@ def _insight_call(cg, a):
2119
2735
  actor = getattr(principal, "actor", None) if principal is not None else None
2120
2736
  if principal is not None and act in ("record", "verify") and not can_write:
2121
2737
  principal.require_admin(f"insight_{act}") # 无写权 → 抛 AccessDenied
2122
- if principal is not None and act in ("reconstruct", "learn") and apply:
2123
- principal.require_admin(f"insight_{act}")
2738
+ if principal is not None and act in ("fork", "branch_rewrite",
2739
+ "branch_merge") and not can_write:
2740
+ principal.require_admin(f"insight_{act}") # 分支写 → 需写权
2741
+ if principal is not None and act in ("reconstruct", "learn",
2742
+ "tickets") and apply:
2743
+ principal.require_admin(f"insight_{act}") # 批量落库 → 需 admin
2744
+ if principal is not None and act == "branch_discard":
2745
+ principal.require_admin("branch_discard") # 弃置:分发层+库层双闸
2124
2746
  return cg.insight(
2125
2747
  action=act, layer=a.get("layer"), limit=a.get("limit"),
2126
2748
  conditions=a.get("conditions"), apply=apply, actor=actor,
2127
2749
  statement=a.get("statement"), category=a.get("category"),
2128
2750
  source=a.get("source"), tags=a.get("tags"),
2129
2751
  importance=a.get("importance"), node_id=a.get("node_id"),
2752
+ content=a.get("content"),
2130
2753
  evidence=a.get("evidence"), v_types=a.get("v_types"),
2131
2754
  verdict=a.get("verdict"), state=a.get("state"),
2132
2755
  window_days=a.get("window_days"), clues=a.get("clues"),
2133
2756
  ids=a.get("ids"), neighbors=a.get("neighbors"),
2757
+ branch_id=a.get("branch_id"), note=a.get("note"),
2758
+ reason=a.get("reason"),
2134
2759
  blindspot_id=a.get("blindspot_id"), horizon=a.get("horizon"),
2135
2760
  max_branches=a.get("max_branches"), recent_days=a.get("recent_days"),
2136
- sample_limit=a.get("sample_limit"), max_nodes=a.get("max_nodes"))
2761
+ sample_limit=a.get("sample_limit"), max_nodes=a.get("max_nodes"),
2762
+ types=a.get("types"), min_blindspot=a.get("min_blindspot"))
2137
2763
 
2138
2764
 
2765
+ # 生效条件:当 cg、a 传入时,按 a.get('action') or 'read'(空串/None 回退 'read')分派:action=stat 返回 {'ok': True, 'action': 'stat', 'ledger': refindex.Ledger(cg.root).summary(), 'note': 'ref 索引水位(_refindex.json):files/nodes 是已登记量;last_index.truncated=true 表示最近一次索引被截断。'};action=check 返回 refindex.check_refs(cg, ledger=refindex.Ledger(cg.root), max_nodes=int(a.get('max_nodes') or refindex.MAX_CHECK)) 的结果并补 action='check' 与 note(max_nodes 假值回落 refindex.MAX_CHECK);action 为 prune/prune_dangling 返回 refindex.prune_dangling(cg, only_roots=a.get('roots'), dry_run=bool(a.get('dry_run')), max_nodes=int(a.get('max_nodes') or refindex.MAX_CHECK)) 的结果并补 action='prune' 与 note(max_nodes 假值回落常量,dry_run 缺键为 False);action 为 read/get 时 nid=(a.get('node_id') or '').strip(),若 nid 非空但 cg.get(nid) 为假返回 {'ok': False, 'error': '节点不存在:nid'},ref 取 a.get('ref') 当且仅当它是 dict,否则若 node 非 None 用 refindex.ref_of(node),若 ref 仍为假返回 {'ok': False, 'error': '该节点没有 code_ref/doc_ref(不是索引节点)'},否则返回 refindex.read_ref(ref, root=a.get('root'), ref_kind=ref_kind) 的结果并设 out['node_id']=nid or None;其他 action 抛 ValueError;
2139
2766
  def _ref_call(cg, a):
2140
2767
  """按 ref 回读被索引的源位置(认知图只存注释/接口,正文在这里取回)。
2141
2768
 
@@ -2204,39 +2831,57 @@ def _ref_call(cg, a):
2204
2831
  return out
2205
2832
 
2206
2833
 
2834
+ # 生效条件:始终 import 白箱模块并返回 whitebox.dispatch(cg, a)。
2207
2835
  def _whitebox_call(cg, a):
2208
2836
  """显式调用白箱能力库(AEIS 降为本地库后的唯一入口)。
2209
2837
 
2210
2838
  显式调用映射:md_cg.whitebox.dispatch
2211
- 见 docs/功能调用映射表_v0.1.md。
2839
+ 见 docs/mdcg/功能调用映射表_v0.1.md。
2212
2840
  """
2213
2841
  from . import whitebox
2214
2842
  return whitebox.dispatch(cg, a)
2215
2843
 
2216
2844
 
2845
+ # 生效条件:op=(a.get("op") or "").strip().lower();op=="relation" 时返回 stg.relation(cg, a.get("a",""), a.get("b",""));op=="timeline" 时返回 stg.timeline(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50), desc=bool(a.get("desc", True)));op=="anchors" 时返回 stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"), layer=a.get("layer"), limit=int(a.get("limit") or 50));op=="consistency" 时返回 stg.consistency(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50));op 为空或其它的值抛 ValueError。
2217
2846
  def _stg_call(cg, a):
2218
- """语义时空图唯一入口。"""
2847
+ """语义时空图唯一入口。
2848
+
2849
+ 四个 op 均支持 `time_axis`:**缺省 `observed`**(与旧行为逐位一致;stg 的
2850
+ 时间语义历来是观察轴),`effective` 走效力轴端点比较。此处与 `cg(op=read)`
2851
+ 的透传策略**相反**——`cg` 侧缺省由库层定(不启用的时间算子),`stg` 侧轴
2852
+ 决定「排序/关系依据」必须**恒有值**,故在此填默认;非法轴仍由
2853
+ `trust.time_axis_of` fail-closed 抛错(本层不预先白名单,避免两套枚举)。
2854
+ """
2219
2855
  from . import stg
2220
2856
  op = (a.get("op") or "").strip().lower()
2857
+ axis = a.get("time_axis") or "observed"
2221
2858
  if op == "relation":
2222
- return stg.relation(cg, a.get("a", ""), a.get("b", ""))
2859
+ return stg.relation(cg, a.get("a", ""), a.get("b", ""), time_axis=axis)
2223
2860
  if op == "timeline":
2224
2861
  return stg.timeline(cg, layer=a.get("layer"),
2225
2862
  limit=int(a.get("limit") or 50),
2226
- desc=bool(a.get("desc", True)))
2863
+ desc=bool(a.get("desc", True)), time_axis=axis)
2227
2864
  if op == "anchors":
2228
2865
  return stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"),
2229
- layer=a.get("layer"), limit=int(a.get("limit") or 50))
2866
+ layer=a.get("layer"), limit=int(a.get("limit") or 50),
2867
+ time_axis=axis)
2230
2868
  if op == "consistency":
2231
2869
  return stg.consistency(cg, layer=a.get("layer"),
2232
- limit=int(a.get("limit") or 50))
2233
- raise ValueError(f"stg 未知 op:{op}")
2870
+ limit=int(a.get("limit") or 50), time_axis=axis)
2871
+ if not op:
2872
+ # fail-closed 且给出可操作提示:stg 的 op 四值签名区分度低于 cg(relation 需
2873
+ # a+b、timeline/anchors/consistency 皆以 layer+limit 为主),**不做签名推导**
2874
+ # ——猜错会静默返回错误视图,比报错更贵。
2875
+ raise ValueError("stg 的 op 为必填参数(缺失即报错,不静默降级);"
2876
+ "允许 op:relation | timeline | anchors | consistency")
2877
+ raise ValueError(f"stg 未知 op:{op}(允许 relation | timeline | anchors | consistency)")
2234
2878
 
2235
2879
 
2236
2880
  # --------------------------------------------------------------------------
2237
2881
  # 工具实现
2238
2882
  # --------------------------------------------------------------------------
2239
2883
 
2884
+ # 生效条件:a=args or {},unit=(a.get("as_unit") or "").strip();unit 为空或 cg.principal 为 None 时直接返回 _dispatch(cg,name,a);否则先 narrowed_principal(cg.principal, unit),TokenError 时返回 {"ok":False,"error":f"as_unit 非法:{e}"},成功则暂存 cg.principal、置为 narrowed、try 返回 _dispatch、finally 还原。
2240
2885
  def call_tool(cg, name, args):
2241
2886
  """MCP tools/call 入口:按 `as_unit` 做**请求级身份收窄**(单进程多身份)。
2242
2887
 
@@ -2269,6 +2914,7 @@ def call_tool(cg, name, args):
2269
2914
  cg.principal = saved
2270
2915
 
2271
2916
 
2917
+ # 生效条件:name 为已注册工具名之一(cg / stg / mdcg_whitebox / mdcg_service_info / mdcg_remember 等);未识别的 name 返回含 error 的响应字典而不抛异常,进程不因此中断;
2272
2918
  def _dispatch(cg, name, args):
2273
2919
  a = args or {}
2274
2920
  if name == "cg":
@@ -2355,7 +3001,8 @@ def _dispatch(cg, name, args):
2355
3001
  include_recent=bool(a.get("include_recent")),
2356
3002
  recent_limit=int(a.get("recent_limit") or 10),
2357
3003
  query_expand=_make_query_expand(a.get("expand")),
2358
- session=a.get("session"))
3004
+ session=a.get("session"),
3005
+ validity=a.get("validity"))
2359
3006
 
2360
3007
  if name == "mdcg_search":
2361
3008
  from . import refindex
@@ -2364,14 +3011,16 @@ def _dispatch(cg, name, args):
2364
3011
  roles=tuple(a["roles"]) if a.get("roles") else None,
2365
3012
  include_work=bool(a.get("include_work")),
2366
3013
  pools=a.get("pools"),
2367
- session=a.get("session"))
3014
+ session=a.get("session"),
3015
+ validity=a.get("validity"))
2368
3016
  return {"meta": meta,
2369
3017
  "results": [{"node": _node_view(n), "score": s, "state": q.get("state"),
2370
3018
  "reason": q.get("reason"), **refindex.ref_fields(n)}
2371
3019
  for n, s, q in res]}
2372
3020
 
2373
3021
  if name == "mdcg_get":
2374
- return _node_view(cg.get(a.get("node_id", "")))
3022
+ return _node_view(cg.get(a.get("node_id", "")),
3023
+ offset=int(a.get("offset") or 0))
2375
3024
 
2376
3025
  if name == "mdcg_reflect":
2377
3026
  res, _ = cg.search(a.get("query", ""), k=int(a.get("k") or 10), record=False)
@@ -2396,10 +3045,18 @@ def _dispatch(cg, name, args):
2396
3045
  a.get("goal", ""))}
2397
3046
 
2398
3047
  if name == "mdcg_propose":
2399
- return {"pid": cg.propose(a.get("node_id", ""), a.get("content", ""),
2400
- layer=a.get("layer") or "knowledge",
2401
- tags=a.get("tags"), condition_space=a.get("condition_space"),
2402
- **_proposal_extras(a))}
3048
+ pr = cg.propose(a.get("node_id", ""), a.get("content", ""), info=True,
3049
+ layer=a.get("layer") or "knowledge",
3050
+ tags=a.get("tags"), condition_space=a.get("condition_space"),
3051
+ **_proposal_extras(a))
3052
+ out = {"pid": pr["pid"], "dedup": bool(pr.get("dedup"))}
3053
+ if pr.get("dedup"):
3054
+ out["dup_of"] = pr["pid"]
3055
+ out["dup_status"] = pr.get("dup_status")
3056
+ out["hint"] = ("同内容提案已存在(pid=%s,状态=%s),幂等返回既有提案、"
3057
+ "未重复入队,无需重试"
3058
+ % (pr["pid"], pr.get("dup_status") or "pending"))
3059
+ return out
2403
3060
 
2404
3061
  if name == "mdcg_review_list":
2405
3062
  return {"pending": cg.review_list()}
@@ -2483,6 +3140,7 @@ def _dispatch(cg, name, args):
2483
3140
  # JSON-RPC / stdio 主循环
2484
3141
  # --------------------------------------------------------------------------
2485
3142
 
3143
+ # 生效条件:error 非 None 时 msg 含 "error"=error、否则含 "result"=result,随后向 sys.stdout 写 _j(msg)+"\n" 并 flush,无返回值。
2486
3144
  def _reply(rid, result=None, error=None):
2487
3145
  msg = {"jsonrpc": "2.0", "id": rid}
2488
3146
  if error is not None:
@@ -2493,6 +3151,7 @@ def _reply(rid, result=None, error=None):
2493
3151
  sys.stdout.flush()
2494
3152
 
2495
3153
 
3154
+ # 生效条件:os.environ.get("MDCG_SUSTAIN","1") 取值属 ("0","false","False") 时返回 None;否则以 name=os.environ.get("MDCG_SUSTAIN_NAME") or "md_cg"、各 interval=float(os.environ.get(...) or sustain.DEFAULT_*)(空串回落默认)、各 auto_* 取 os.environ.get 默认值且值属 ("0","false","False") 时为关,调 sustain.ensure_loop 后 lp.start() 并返回 lp。
2496
3155
  def _start_sustain(cg):
2497
3156
  """启动常驻自维持循环(MDCG_SUSTAIN=0 关闭;间隔可用环境变量调)。"""
2498
3157
  if os.environ.get("MDCG_SUSTAIN", "1") in ("0", "false", "False"):
@@ -2525,6 +3184,7 @@ def _start_sustain(cg):
2525
3184
  return lp
2526
3185
 
2527
3186
 
3187
+ # 生效条件:s=str(s or "").strip();不以 "session-" 开头、或去前缀按 "-" 分组后长度不等于 [8,4,4,4,12]、或某组字符不都在 "0123456789abcdef" 时返回 False,三者皆过返回 True。
2528
3188
  def _is_dsh_session(s: str) -> bool:
2529
3189
  """DSH 会话 id 形态判定:session-<8>-<4>-<4>-<4>-<12>(uuid4)。"""
2530
3190
  s = str(s or "").strip()
@@ -2536,6 +3196,7 @@ def _is_dsh_session(s: str) -> bool:
2536
3196
  return all(all(c in "0123456789abcdef" for c in g) for g in groups)
2537
3197
 
2538
3198
 
3199
+ # 生效条件:s=str(raw or "").strip();s 为空返回 "anonymous";非 DSH 形态返回 s;DSH 形态时以 root=os.environ.get("MDCG_DSH_SESSIONS_ROOT") or ~/.dsh/sessions 遍历条目,存在 root/<name>/s 目录则返回 s,os.listdir 抛 OSError 时返回 s,否则返回 "anonymous"。
2539
3200
  def _normalize_session(raw):
2540
3201
  """会话 id 归一 + 轻校验(只影响归因,不影响写入)。
2541
3202
 
@@ -2563,6 +3224,7 @@ def _normalize_session(raw):
2563
3224
  return "anonymous"
2564
3225
 
2565
3226
 
3227
+ # 生效条件:环境变量 MDCG_SESSION(优先)或 DSH_SESSION_ID 去空白后非空时把 p.session 设为 _normalize_session(raw),MDCG_HARNESS 去空白后非空时把 p.harness 设为该值,MDCG_UNIT 去空白后非空时把 p.unit 设为该值,三者均为空串或未设置时 p 的对应字段保持原值;
2566
3228
  def _apply_attribution(p):
2567
3229
  """归因维度注入(嵌套身份:(harness, session)),**不参与授权**。
2568
3230
 
@@ -2583,6 +3245,7 @@ def _apply_attribution(p):
2583
3245
  p.unit = unit
2584
3246
 
2585
3247
 
3248
+ # 生效条件:环境变量 MDCG_TOKEN 去空白后非空时经 verify_token(token, tenant=os.environ.get("MDCG_TENANT")) 构造——抛 TokenError 则返回 (None, f"令牌校验失败:{e}"),成功则返回 (_attach_theory(p), None);否则 MDCG_LEGACY_ENV_AUTH 值为 "1"/"true"/"True" 时按 MDCG_CAN_ADMIN/MDCG_CAN_WRITE 落到 designer/recorder/output 角色的 legacy_env 身份,两者都不满足时构造 can_write=False、can_admin=False、auth_mode="anonymous" 的 guest 身份,后两条路径同样返回 (_attach_theory(p), None);
2586
3249
  def _build_principal():
2587
3250
  """构造 Principal(令牌优先,fail-closed)。返回 (principal, error)。
2588
3251
 
@@ -2633,6 +3296,7 @@ def _build_principal():
2633
3296
  return _attach_theory(p), None
2634
3297
 
2635
3298
 
3299
+ # 生效条件:p 传入后调用 theory.ensure() 得到 st,把 p.theory_ok 置为 bool(st.get("theory_ok"))(缺键或假值均为 False,而非保留原值或 None),p.theory_version 置为 st.get("version")(缺键为 None),返回 p,且校验过程不抛异常;
2636
3300
  def _attach_theory(p):
2637
3301
  """附加版本层状态(theory_ok / theory_version)。校验永不抛异常。
2638
3302
 
@@ -2645,6 +3309,7 @@ def _attach_theory(p):
2645
3309
  return p
2646
3310
 
2647
3311
 
3312
+ # 生效条件:环境变量 MDCG_ROOT 非空且其 os.path.abspath 规范化后的 basename 小写不以 "_md_cg_" 开头、且 _build_principal() 返回的 err 为空时,构造 MdCGSecure(root, principal=principal, autoflush=1) 并对 sys.stdin 逐行 method 分派(initialize 回 protocolVersion=PROTOCOL_VERSION 与 SERVER_NAME/SERVER_VERSION,tools/list 回 tools_for_surface(),tools/call 经 call_tool 后回 content,shutdown 跳出循环),遍历结束后调用 sustain.stop_all() 与 cg.close() 并返回 0;MDCG_ROOT 为空或 basename 命中 "_md_cg_" 前缀返回 2,令牌校验失败返回 3;
2648
3313
  def main():
2649
3314
  root = os.environ.get("MDCG_ROOT")
2650
3315
  if not root:
@@ -2664,6 +3329,24 @@ def main():
2664
3329
  "[mdcg-mcp] 请指向主认知图目录(如仓库内 md_cg/)。\n")
2665
3330
  return 2
2666
3331
  from .mdcos import MdCGSecure
3332
+ # 数据面迁出插件包(issue #18 相邻问题):旧版把运行时数据与路径配置写在包内
3333
+ # `<pkg>/data`,pnpm 更新会连目录一起替换(实机实证 data/ 54 文件 → 0)。首启把
3334
+ # 「旧位置仍有货」的数据**复制**到用户级数据根——node 侧插件启动时已做过一次,
3335
+ # 这里是 python 独立使用(不经插件)时的同口径兜底。fail-safe:迁移只是增益,
3336
+ # 任何失败都不阻塞启动,但不静默(走 stderr)。
3337
+ try:
3338
+ from .datapath import migrate_legacy_data as _migrate_legacy
3339
+ _mig = _migrate_legacy()
3340
+ if _mig.get("ran"):
3341
+ sys.stderr.write(
3342
+ "[mdcg-mcp] 数据面已迁出插件包(旧位置只复制未删除):%s → %s"
3343
+ "(%d 项)\n" % (_mig["from"], _mig["to"], _mig["copied"]))
3344
+ elif _mig.get("failed"):
3345
+ sys.stderr.write("[mdcg-mcp] 数据面迁移部分失败(不阻塞启动):%s\n"
3346
+ % (_mig["failed"],))
3347
+ except Exception as _mig_exc: # 迁移故障不得影响服务可用性
3348
+ sys.stderr.write("[mdcg-mcp] 数据面迁移检查失败(不阻塞启动): %r\n"
3349
+ % (_mig_exc,))
2667
3350
  principal, err = _build_principal()
2668
3351
  if err:
2669
3352
  sys.stderr.write(
@@ -2678,7 +3361,14 @@ def main():
2678
3361
  _p_session = os.environ.get("MDCG_SESSION", "").strip()
2679
3362
  if _p_session:
2680
3363
  principal.session = _p_session
2681
- cg = MdCGSecure(root, principal=principal)
3364
+ # 索引可见性(2026-09-16):autoflush=1 —— 逐条写入立即落分片日志
3365
+ # `_index_log/`。根因(第4条取证):默认 autoflush=64 且常驻进程不 close,
3366
+ # 单条写入在达阈值前**对其他进程不可见**(`_load_index` = 快照 + 分片日志
3367
+ # 重放;`_index.json` 只在 compact/rebuild 时重写),表现为「写入返回
3368
+ # committed=true 但第16条读回确认在跨进程读面上不可检索」。本参数是
3369
+ # server 级兜底:覆盖不经 writepipe 写链的路径(propose / remember_gated /
3370
+ # add_rejected 等);写链另有显式提交边界(writepipe._commit_visibility)。
3371
+ cg = MdCGSecure(root, principal=principal, autoflush=1)
2682
3372
  # 外部验证器注入(**能力外置**):识图/实测/验收等能力不在认知图内,
2683
3373
  # 由 MDCG_VERIFIER_MODULES(逗号分隔 import 路径)声明的外部模块注入,
2684
3374
  # 通常位于私有运行时仓。加载失败不阻塞启动——失败原因写进 stderr 与
@@ -2688,6 +3378,42 @@ def main():
2688
3378
  if _verifiers.get("loaded") or _verifiers.get("failed"):
2689
3379
  sys.stderr.write("[mdcg-mcp] 外部验证器: loaded=%s failed=%s\n"
2690
3380
  % (_verifiers.get("loaded"), _verifiers.get("failed")))
3381
+ # ③ 两段式对账:上个进程若被杀在「落盘前后」之间,账本里会留下「有意图
3382
+ # 无结果」的写入——启动即清账(内容确已落盘 → 补账 committed;没落上 →
3383
+ # 如实标记 interrupted)。失败**不阻塞启动**:对账是增益,不是服务前提,
3384
+ # 但必须上报(stderr),不静默。
3385
+ try:
3386
+ _rep = cg.reconcile_writes()
3387
+ if _rep.get("unpaired"):
3388
+ sys.stderr.write("[mdcg-mcp] 写入两段式对账: 未结清=%s 补账=%s "
3389
+ "中断=%s\n" % (_rep.get("unpaired"),
3390
+ _rep.get("committed"),
3391
+ _rep.get("interrupted")))
3392
+ except Exception as _exc: # 对账故障不得影响服务可用性
3393
+ sys.stderr.write("[mdcg-mcp] 两段式对账失败(不阻塞启动): %r\n"
3394
+ % (_exc,))
3395
+ # 进程自报(诊断设施,fail-safe):把「md_cg 实际加载源 + render 契约代际」
3396
+ # 落成 `<tempdir>/md_cg_servers/<pid>.json`,供 `scripts/mdcg_stale_servers.py`
3397
+ # 机械判定**活进程代际**。根因(第4条取证):原先只能比「进程启动时间 vs
3398
+ # md_cg/*.py 最新 mtime」,该判据有活体盲区——npm 副本进程启动于 11:37~11:39
3399
+ # 而源码 mtime 为 09:24,故 stale=False,却持插件包 0.4.8 的旧契约
3400
+ # `codeindex.render`,把全量重建成果刷回 old_synth(AGENTS.md §5 运维注记)。
3401
+ # 自报给的是**绝对事实**(进程自己报出实际加载源),不再依赖相对量推断。
3402
+ # 失败不阻塞启动:自报是增益,不是服务前提,但必须上报(stderr),不静默。
3403
+ try:
3404
+ from . import selfreport
3405
+ _sr = selfreport.report(tag="mcp_server")
3406
+ if _sr:
3407
+ sys.stderr.write(
3408
+ "[mdcg-mcp] 自报: source=%s render_version=%s\n"
3409
+ "[mdcg-mcp] 自报文件: %s\n"
3410
+ % (_sr.get("source_dir"), _sr.get("render_version"),
3411
+ _sr.get("self_report_path")))
3412
+ else:
3413
+ sys.stderr.write("[mdcg-mcp] 自报写入失败(不阻塞启动;"
3414
+ "该进程的代际将无法被外部机械判定)\n")
3415
+ except Exception as _exc: # noqa: BLE001 —— 自报不得阻塞启动
3416
+ sys.stderr.write("[mdcg-mcp] 自报异常(不阻塞启动): %r\n" % (_exc,))
2691
3417
  _start_sustain(cg)
2692
3418
 
2693
3419
  for line in sys.stdin: