@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -3,7 +3,7 @@
3
3
  * 已与驱动器解耦,随时可换 loopx tick):
4
4
  * migrate [root] one-shot 迁移:旧 JSON/JSONL → 账本 events
5
5
  * (导入前自动快照到 gotry-state/pre-ledger-backup/)
6
- * export [root] 账本 → 旧文件名视图(红线 6:可见可导出;单向,不回流)
6
+ * export [root] local-only:账本 → 旧文件名视图(红线 6:可见可导出;单向,不回流)
7
7
  * log [root] [--limit N] 事件账本尾部(append-only 审计面)
8
8
  * stats [root] 各面计数
9
9
  * rebuild [root] [toSeq] DROP 投影 → fold 重放(可截到 toSeq;账本/投影
@@ -13,39 +13,365 @@
13
13
  * forget [root] wish|companion|motivation <id>
14
14
  * 物理硬删该主体全部事件 + 审计一行 + 重建投影
15
15
  * (红线 6「可删除」;D5 默认物理删)
16
- * tick [root] 回收全部 pending 工单(durable 恢复语义)
17
- * whatif [root] <dest.db> VACUUM INTO 分叉副本(WriteGate what-if 预演,不触正本)
16
+ * tick [root] local-only:回收全部 pending 工单(durable 恢复语义)
17
+ * whatif [root] <dest.db> local-only:VACUUM INTO 整库分叉副本(管理员 snapshot;
18
+ * 不是租户 export,不触正本)
18
19
  * pw-list [root] pending_writes 清单
19
20
  * pw-request [root] <idemKey> <seam> <payloadJson> 登记待确认外部写(L2)
20
21
  * pw-confirm [root] <idemKey> <receipt> 具名 seam 确认(L3,携 receipt)
21
22
  * pw-compensate [root] <idemKey> <note> saga 补偿
23
+ * repair-plan [root] [--mapping <file>] [--format text|json]
24
+ * 只读 inventory + dry-run tenant 修复计划(#254):
25
+ * 正本零写(只在临时副本上读),无显式证据映射的
26
+ * 事件一律原地保留;真实 apply 在单独 owner gate 后
27
+ * repair-apply [root] --mapping <file> --plan-digest <hex>
28
+ * --i-authorize-apply [--format text|json] [--receipt-out <file>]
29
+ * 授权 apply(#254):写前 backup + CAS 搬移 + 投影重建
30
+ * repair-rollback [root] --backup <dir> --i-authorize-rollback
31
+ * 用校验过的 backup 还原正本(#254)
32
+ *
33
+ * 选项(可放在命令前或命令后,也可夹在位置参数之间):
34
+ * --state-root <root> 显式 state root;省略时按命令沿用位置参数 root 或默认 '.'。
35
+ * --tenant <tenant> 账本租户 scope(默认 local);这是范围参数,不是认证/授权。
36
+ * --limit <N> 仅 log 支持;N 必须是正整数。
37
+ * --mapping <file> repair-plan / repair-apply;人工确认的证据映射 JSON 数组。
38
+ * --format text|json repair-plan / repair-apply;默认 text。
39
+ * --plan-digest <hex> 仅 repair-apply;必须与重算计划 digest 一致。
40
+ * --i-authorize-apply 仅 repair-apply;显式授权写路径(布尔旗标)。
41
+ * --backup <dir> 仅 repair-rollback;校验过的 backup 目录。
42
+ * --i-authorize-rollback 仅 repair-rollback;显式授权还原(布尔旗标)。
43
+ * --receipt-out <file> 仅 repair-apply;写出脱敏回执 JSON。
44
+ *
22
45
  * root 默认 '.';运行:cd ts && npx tsx scripts/state-cli.ts <cmd> ...
23
46
  */
24
47
 
25
48
  import { mkdirSync, renameSync, writeFileSync } from 'node:fs'
26
49
  import { dirname, join } from 'node:path'
27
- import { ensureLedger, openLedgerIfExists } from '../src/state-ledger.ts'
50
+ import { ensureLedger, ledgerDbPath, ledgerExists, openLedgerIfExists } from '../src/state-ledger.ts'
51
+ import { formatRepairPlan, loadEvidenceMappingFile, planLedgerRepair } from '../src/ledger-repair-plan.ts'
52
+ import {
53
+ applyLedgerRepair,
54
+ formatRepairApplyResult,
55
+ restoreRepairBackup,
56
+ } from '../src/ledger-repair-apply.ts'
28
57
  import { collectDeepPlanning, makeJournaledSolvePort, settleAsyncTicket, type AsyncTicket } from '../src/loop.ts'
29
58
  import { solveUnified } from '../src/unified.ts'
30
59
  import type { TripState } from '../src/contracts.ts'
31
60
 
32
- const [cmd, ...rest] = process.argv.slice(2)
61
+ const HELP = `用法: npx tsx scripts/state-cli.ts [--state-root <root>] [--tenant <tenant>] <cmd> [args...]
62
+ 命令:
63
+ migrate [root]
64
+ export [root] # local-only:导出共享 legacy 文件名视图
65
+ log [root] [--limit N]
66
+ stats [root]
67
+ rebuild [root] [toSeq]
68
+ rewind [root] <seq>
69
+ forget [root] wish|companion|motivation <id>
70
+ tick [root] # local-only:回收本地 pending 工单
71
+ whatif [root] <dest.db> # local-only:整库管理员 snapshot,不是租户 export
72
+ pw-list [root]
73
+ pw-request [root] <idemKey> <seam> <payloadJson>
74
+ pw-confirm [root] <idemKey> <receipt>
75
+ pw-compensate [root] <idemKey> <note>
76
+ repair-plan [root] [--mapping <file>] [--format text|json] # 只读 dry-run,零写
77
+ repair-apply [root] --mapping <file> --plan-digest <hex> --i-authorize-apply [--receipt-out <file>]
78
+ repair-rollback [root] --backup <dir> --i-authorize-rollback
79
+ 选项:
80
+ --state-root <root> 显式 state root;保留位置参数 root 用法
81
+ --tenant <tenant> 默认 local;仅是账本 scope,不是认证/授权
82
+ --limit <N> 仅 log 支持,N 为正整数
83
+ --mapping <file> repair-plan/repair-apply,人工确认的证据映射 JSON 数组
84
+ --format text|json repair-plan/repair-apply,默认 text
85
+ --plan-digest <hex> 仅 repair-apply
86
+ --i-authorize-apply 仅 repair-apply(布尔旗标)
87
+ --backup <dir> 仅 repair-rollback
88
+ --i-authorize-rollback 仅 repair-rollback(布尔旗标)
89
+ --receipt-out <file> 仅 repair-apply
90
+ 边界:tick/export/whatif 只支持 --tenant local;非 local 会在创建目录、打开账本、求解或写文件前拒绝。
91
+ repair-plan 全程只读:不建库、不迁移 schema、不改任何 ledger/projection。
92
+ db/-wal/-shm 在复制或读取期间若有变化会 fail-closed;请对静默或不可变副本重试。
93
+ repair-apply/repair-rollback 是显式授权写路径;无旗标或 digest 不匹配则零写失败。
94
+ 提示:root 路径若像数字/负数或以 '-' 开头,请用 --state-root <root> 明示。`
33
95
 
34
- function rootOf(args: string[]): string {
35
- const i = args.indexOf('--state-root')
36
- if (i >= 0 && args[i + 1]) return args[i + 1]
37
- const first = args.find(a => !a.startsWith('--') && !/^[\d]+$/.test(a))
38
- return first ?? '.'
96
+ const COMMANDS = new Set([
97
+ 'migrate',
98
+ 'export',
99
+ 'log',
100
+ 'stats',
101
+ 'rebuild',
102
+ 'rewind',
103
+ 'forget',
104
+ 'tick',
105
+ 'whatif',
106
+ 'pw-list',
107
+ 'pw-request',
108
+ 'pw-confirm',
109
+ 'pw-compensate',
110
+ 'repair-plan',
111
+ 'repair-apply',
112
+ 'repair-rollback',
113
+ ])
114
+ const OPTION_NAMES = new Set([
115
+ '--state-root',
116
+ '--tenant',
117
+ '--limit',
118
+ '--mapping',
119
+ '--format',
120
+ '--plan-digest',
121
+ '--backup',
122
+ '--receipt-out',
123
+ ])
124
+ const FLAG_NAMES = new Set(['--i-authorize-apply', '--i-authorize-rollback'])
125
+ const LOCAL_ONLY_COMMANDS = new Set(['export', 'tick', 'whatif'])
126
+ const SUBJECTS = new Set(['wish', 'companion', 'motivation'])
127
+
128
+ type OptionName =
129
+ | '--state-root'
130
+ | '--tenant'
131
+ | '--limit'
132
+ | '--mapping'
133
+ | '--format'
134
+ | '--plan-digest'
135
+ | '--backup'
136
+ | '--receipt-out'
137
+ type FlagName = '--i-authorize-apply' | '--i-authorize-rollback'
138
+
139
+ interface ParsedCli {
140
+ cmd?: string
141
+ root: string
142
+ tenant: string
143
+ limit: number
144
+ mapping?: string
145
+ format: 'text' | 'json'
146
+ planDigest?: string
147
+ backup?: string
148
+ receiptOut?: string
149
+ authorizeApply: boolean
150
+ authorizeRollback: boolean
151
+ positional: string[]
152
+ }
153
+
154
+ function usageError(message: string): never {
155
+ console.error(message)
156
+ console.error(HELP)
157
+ process.exit(1)
39
158
  }
40
159
 
41
- function tenantOf(args: string[]): string {
42
- const i = args.indexOf('--tenant')
43
- return i >= 0 && args[i + 1] ? args[i + 1] : 'local'
160
+ function isOptionName(token: string): token is OptionName {
161
+ return OPTION_NAMES.has(token)
44
162
  }
45
163
 
46
- function arg(name: string, fallback?: string): string | undefined {
47
- const i = process.argv.indexOf(name)
48
- return i >= 0 ? process.argv[i + 1] : fallback
164
+ function isFlagName(token: string): token is FlagName {
165
+ return FLAG_NAMES.has(token)
166
+ }
167
+
168
+ function parsePositiveInteger(raw: string, label: string): number {
169
+ if (!/^\d+$/.test(raw)) usageError(`${label} 必须是正整数:${raw}`)
170
+ const n = Number(raw)
171
+ if (!Number.isSafeInteger(n) || n <= 0) usageError(`${label} 必须是正整数:${raw}`)
172
+ return n
173
+ }
174
+
175
+ function parseSequence(raw: string, label: string): number {
176
+ if (!/^\d+$/.test(raw)) usageError(`${label} 必须是非负整数:${raw}`)
177
+ const n = Number(raw)
178
+ if (!Number.isSafeInteger(n)) usageError(`${label} 超出安全整数范围:${raw}`)
179
+ return n
180
+ }
181
+
182
+ function looksLikeNumeric(raw: string): boolean {
183
+ return /^[+-]?(?:\d|\.\d)/.test(raw)
184
+ }
185
+
186
+ function rootAndArgs(cmd: string, positional: string[], explicitRoot?: string): { root: string; args: string[] } {
187
+ if (explicitRoot !== undefined) return { root: explicitRoot, args: positional }
188
+
189
+ switch (cmd) {
190
+ case 'migrate':
191
+ case 'export':
192
+ case 'log':
193
+ case 'stats':
194
+ case 'tick':
195
+ case 'pw-list':
196
+ case 'repair-plan':
197
+ case 'repair-apply':
198
+ case 'repair-rollback': {
199
+ if (positional.length > 1) usageError(`${cmd} 只接受一个可选 root 位置参数`)
200
+ return { root: positional[0] ?? '.', args: [] }
201
+ }
202
+ case 'rebuild':
203
+ case 'rewind': {
204
+ const rootIndex = positional.findIndex(p => !looksLikeNumeric(p))
205
+ if (rootIndex < 0) return { root: '.', args: positional }
206
+ const root = positional[rootIndex]!
207
+ return { root, args: positional.filter((_, i) => i !== rootIndex) }
208
+ }
209
+ case 'forget':
210
+ case 'whatif':
211
+ case 'pw-request':
212
+ case 'pw-confirm':
213
+ case 'pw-compensate': {
214
+ if (positional.length === 0) return { root: '.', args: [] }
215
+ return { root: positional[0]!, args: positional.slice(1) }
216
+ }
217
+ default:
218
+ return { root: '.', args: positional }
219
+ }
220
+ }
221
+
222
+ function validateArgs(
223
+ cmd: string,
224
+ args: string[],
225
+ opts: {
226
+ limitRaw?: string
227
+ mappingRaw?: string
228
+ formatRaw?: string
229
+ planDigest?: string
230
+ backup?: string
231
+ receiptOut?: string
232
+ authorizeApply: boolean
233
+ authorizeRollback: boolean
234
+ },
235
+ ): { limit: number; format: 'text' | 'json' } {
236
+ if (opts.limitRaw !== undefined && cmd !== 'log') usageError(`${cmd} 不支持 --limit`)
237
+ if (opts.mappingRaw !== undefined && cmd !== 'repair-plan' && cmd !== 'repair-apply') {
238
+ usageError(`${cmd} 不支持 --mapping`)
239
+ }
240
+ if (opts.formatRaw !== undefined && cmd !== 'repair-plan' && cmd !== 'repair-apply') {
241
+ usageError(`${cmd} 不支持 --format`)
242
+ }
243
+ if (opts.planDigest !== undefined && cmd !== 'repair-apply') usageError(`${cmd} 不支持 --plan-digest`)
244
+ if (opts.backup !== undefined && cmd !== 'repair-rollback') usageError(`${cmd} 不支持 --backup`)
245
+ if (opts.receiptOut !== undefined && cmd !== 'repair-apply') usageError(`${cmd} 不支持 --receipt-out`)
246
+ if (opts.authorizeApply && cmd !== 'repair-apply') usageError(`${cmd} 不支持 --i-authorize-apply`)
247
+ if (opts.authorizeRollback && cmd !== 'repair-rollback') usageError(`${cmd} 不支持 --i-authorize-rollback`)
248
+
249
+ const limit = opts.limitRaw === undefined ? 20 : parsePositiveInteger(opts.limitRaw, '--limit')
250
+ if (opts.formatRaw !== undefined && opts.formatRaw !== 'text' && opts.formatRaw !== 'json') {
251
+ usageError(`--format 只支持 text|json:${opts.formatRaw}`)
252
+ }
253
+ const format: 'text' | 'json' = opts.formatRaw === 'json' ? 'json' : 'text'
254
+
255
+ switch (cmd) {
256
+ case 'migrate':
257
+ case 'export':
258
+ case 'log':
259
+ case 'stats':
260
+ case 'tick':
261
+ case 'pw-list':
262
+ case 'repair-plan':
263
+ case 'repair-apply':
264
+ case 'repair-rollback':
265
+ if (args.length !== 0) usageError(`${cmd} 不接受额外位置参数:${args.join(' ')}`)
266
+ break
267
+ case 'rebuild':
268
+ if (args.length > 1) usageError('rebuild 最多接受一个 toSeq')
269
+ if (args[0] !== undefined) parseSequence(args[0], 'toSeq')
270
+ break
271
+ case 'rewind':
272
+ if (args.length !== 1) usageError('rewind 需要且只接受一个 seq')
273
+ parseSequence(args[0]!, 'seq')
274
+ break
275
+ case 'forget':
276
+ if (args.length !== 2) usageError('forget 需要 subject 与 id;省略 root 时请用 --state-root . 明示')
277
+ if (!SUBJECTS.has(args[0]!)) usageError(`未知主体 ${args[0]}(wish|companion|motivation)`)
278
+ break
279
+ case 'whatif':
280
+ if (args.length !== 1) usageError('whatif 需要 dest.db;省略 root 时请用 --state-root . 明示')
281
+ break
282
+ case 'pw-request':
283
+ if (args.length !== 3) usageError('pw-request 需要 idemKey、seam 与 payloadJson;省略 root 时请用 --state-root . 明示')
284
+ break
285
+ case 'pw-confirm':
286
+ if (args.length !== 2) usageError('pw-confirm 需要 idemKey 与 receipt;省略 root 时请用 --state-root . 明示')
287
+ break
288
+ case 'pw-compensate':
289
+ if (args.length !== 2) usageError('pw-compensate 需要 idemKey 与 note;省略 root 时请用 --state-root . 明示')
290
+ break
291
+ }
292
+
293
+ if (cmd === 'repair-apply') {
294
+ if (opts.mappingRaw === undefined) usageError('repair-apply 需要 --mapping <file>')
295
+ if (opts.planDigest === undefined) usageError('repair-apply 需要 --plan-digest <hex>')
296
+ if (!opts.authorizeApply) usageError('repair-apply 需要显式 --i-authorize-apply')
297
+ }
298
+ if (cmd === 'repair-rollback') {
299
+ if (opts.backup === undefined) usageError('repair-rollback 需要 --backup <dir>')
300
+ if (!opts.authorizeRollback) usageError('repair-rollback 需要显式 --i-authorize-rollback')
301
+ }
302
+
303
+ return { limit, format }
304
+ }
305
+
306
+ function parseCli(argv: string[]): ParsedCli {
307
+ let cmd: string | undefined
308
+ const positional: string[] = []
309
+ const options = new Map<OptionName, string>()
310
+ const flags = new Set<FlagName>()
311
+
312
+ for (let i = 0; i < argv.length; i++) {
313
+ const token = argv[i]!
314
+ if (token.startsWith('--')) {
315
+ if (isFlagName(token)) {
316
+ if (flags.has(token)) usageError(`重复选项 ${token}`)
317
+ flags.add(token)
318
+ continue
319
+ }
320
+ if (!isOptionName(token)) usageError(`未知选项 ${token}`)
321
+ if (options.has(token)) usageError(`重复选项 ${token}`)
322
+ const value = argv[i + 1]
323
+ if (value === undefined || value === '' || value.startsWith('--')) usageError(`${token} 缺少值`)
324
+ options.set(token, value)
325
+ i++
326
+ continue
327
+ }
328
+ if (token.startsWith('-') && !looksLikeNumeric(token)) usageError(`未知选项 ${token}`)
329
+ if (cmd === undefined) cmd = token
330
+ else positional.push(token)
331
+ }
332
+
333
+ if (cmd === undefined) usageError('缺少命令')
334
+ if (!COMMANDS.has(cmd)) {
335
+ return {
336
+ cmd, root: '.', tenant: 'local', limit: 20, format: 'text',
337
+ authorizeApply: false, authorizeRollback: false, positional,
338
+ }
339
+ }
340
+
341
+ const tenant = options.get('--tenant') ?? 'local'
342
+ if (tenant.trim() === '') usageError('--tenant 缺少值')
343
+ const explicitRoot = options.get('--state-root')
344
+ if (explicitRoot !== undefined && explicitRoot.trim() === '') usageError('--state-root 缺少值')
345
+ const { root, args } = rootAndArgs(cmd, positional, explicitRoot)
346
+ const authorizeApply = flags.has('--i-authorize-apply')
347
+ const authorizeRollback = flags.has('--i-authorize-rollback')
348
+ const { limit, format } = validateArgs(cmd, args, {
349
+ limitRaw: options.get('--limit'),
350
+ mappingRaw: options.get('--mapping'),
351
+ formatRaw: options.get('--format'),
352
+ planDigest: options.get('--plan-digest'),
353
+ backup: options.get('--backup'),
354
+ receiptOut: options.get('--receipt-out'),
355
+ authorizeApply,
356
+ authorizeRollback,
357
+ })
358
+ if (LOCAL_ONLY_COMMANDS.has(cmd) && tenant !== 'local') {
359
+ usageError(`${cmd} 仅支持 --tenant local;该命令未实现租户隔离,已在创建目录、打开账本、求解或写文件前拒绝。`)
360
+ }
361
+ return {
362
+ cmd,
363
+ root,
364
+ tenant,
365
+ limit,
366
+ mapping: options.get('--mapping'),
367
+ format,
368
+ planDigest: options.get('--plan-digest'),
369
+ backup: options.get('--backup'),
370
+ receiptOut: options.get('--receipt-out'),
371
+ authorizeApply,
372
+ authorizeRollback,
373
+ positional: args,
374
+ }
49
375
  }
50
376
 
51
377
  function atomicWrite(path: string, text: string): void {
@@ -54,12 +380,11 @@ function atomicWrite(path: string, text: string): void {
54
380
  renameSync(tmp, path)
55
381
  }
56
382
 
57
- const HELP = '用法见文件头注释(npx tsx scripts/state-cli.ts <cmd> [root] ...)'
383
+ const parsed = parseCli(process.argv.slice(2))
384
+ const { cmd, root, tenant } = parsed
58
385
 
59
386
  switch (cmd) {
60
387
  case 'migrate': {
61
- const root = rootOf(rest)
62
- const tenant = tenantOf(rest)
63
388
  const before = openLedgerIfExists(root, tenant)?.countEvents() ?? 0
64
389
  const ledger = ensureLedger(root, tenant)
65
390
  const backupDir = join(root, 'gotry-state', 'pre-ledger-backup')
@@ -67,8 +392,6 @@ switch (cmd) {
67
392
  break
68
393
  }
69
394
  case 'export': {
70
- const root = rootOf(rest)
71
- const tenant = tenantOf(rest)
72
395
  const ledger = ensureLedger(root, tenant)
73
396
  const dir = join(root === '.' ? process.cwd() : root, 'gotry-state')
74
397
  mkdirSync(dir, { recursive: true })
@@ -86,19 +409,14 @@ switch (cmd) {
86
409
  break
87
410
  }
88
411
  case 'log': {
89
- const root = rootOf(rest)
90
- const tenant = tenantOf(rest)
91
- const limit = Number(arg('--limit', '20'))
92
412
  const ledger = openLedgerIfExists(root, tenant)
93
413
  if (!ledger) { console.log('(无账本——未迁移 root,旧文件形态)'); break }
94
- for (const e of ledger.readEvents(undefined, limit)) {
414
+ for (const e of ledger.readEvents(undefined, parsed.limit)) {
95
415
  console.log(`${String(e.seq).padStart(5)} ${e.ts} ${e.actor.padEnd(28)} ${e.kind.padEnd(24)} ${e.subject_id}${e.idem_key ? ` #${e.idem_key}` : ''}`)
96
416
  }
97
417
  break
98
418
  }
99
419
  case 'stats': {
100
- const root = rootOf(rest)
101
- const tenant = tenantOf(rest)
102
420
  const ledger = openLedgerIfExists(root, tenant)
103
421
  if (!ledger) { console.log('(无账本——未迁移 root)'); break }
104
422
  console.log(`events=${ledger.countEvents()} wishes=${ledger.readWishPool().length} companions=${ledger.readCompanions().length} trips=${ledger.readTrips().length} utility=${ledger.readUtilityEvents().length} pendingRuns=${ledger.pendingWorkflowRuns().length} pendingWrites=${ledger.listPendingWrites().length}`)
@@ -106,37 +424,30 @@ switch (cmd) {
106
424
  }
107
425
  case 'rebuild':
108
426
  case 'rewind': {
109
- const root = rootOf(rest)
110
- const tenant = tenantOf(rest)
111
427
  const ledger = openLedgerIfExists(root, tenant)
112
428
  if (!ledger) { console.error('无账本'); process.exit(1) }
113
- const toSeqRaw = rest.find(a => /^\d+$/.test(a))
114
- const r = ledger.rebuildProjections(toSeqRaw ? Number(toSeqRaw) : undefined)
429
+ const toSeqRaw = parsed.positional[0]
430
+ const toSeq = toSeqRaw === undefined ? undefined : parseSequence(toSeqRaw, 'seq')
431
+ const r = ledger.rebuildProjections(toSeq)
115
432
  console.log(`fold 重建完成${toSeqRaw ? `(至 seq ${toSeqRaw};rebuild 无参即回最新)` : '(全量)'}:重放 ${r.events} 事件 → 愿望 ${r.wishes} / 同行人 ${r.companions}`)
116
433
  break
117
434
  }
118
435
  case 'forget': {
119
- const root = rootOf(rest)
120
- const tenant = tenantOf(rest)
121
436
  const ledger = openLedgerIfExists(root, tenant)
122
437
  if (!ledger) { console.error('无账本'); process.exit(1) }
123
- const subject = rest.find(a => !a.startsWith('--') && a !== root)
124
- const id = rest[rest.indexOf(subject!) + 1]
125
- if (!subject || id === undefined) { console.error(HELP); process.exit(1) }
438
+ const [subject, id] = parsed.positional
126
439
  const map: Record<string, { kinds: string[]; subjectId: string }> = {
127
- wish: { kinds: ['wish.imported', 'wish.added', 'wish.updated', 'memory_utility.event'], subjectId: id },
128
- companion: { kinds: ['companion.imported', 'companion.saved'], subjectId: id },
440
+ wish: { kinds: ['wish.imported', 'wish.added', 'wish.updated', 'memory_utility.event'], subjectId: id! },
441
+ companion: { kinds: ['companion.imported', 'companion.saved'], subjectId: id! },
129
442
  motivation: { kinds: ['motivation.imported', 'motivation.patch'], subjectId: 'motivation' },
130
443
  }
131
- const spec = map[subject]
444
+ const spec = map[subject!]
132
445
  if (!spec) { console.error(`未知主体 ${subject}(wish|companion|motivation)`); process.exit(1) }
133
446
  const r = ledger.forgetSubject([spec])
134
447
  console.log(`已物理硬删 ${r.deleted} 条事件并重建投影(审计一行留痕;红线 6「可删除」)`)
135
448
  break
136
449
  }
137
450
  case 'tick': {
138
- const root = rootOf(rest)
139
- const tenant = tenantOf(rest)
140
451
  const ledger = openLedgerIfExists(root, tenant)
141
452
  if (!ledger) { console.log('(无账本,无待办)'); break }
142
453
  const pending = ledger.pendingWorkflowRuns()
@@ -153,20 +464,15 @@ switch (cmd) {
153
464
  break
154
465
  }
155
466
  case 'whatif': {
156
- const root = rootOf(rest)
157
- const tenant = tenantOf(rest)
158
467
  const ledger = openLedgerIfExists(root, tenant)
159
468
  if (!ledger) { console.error('无账本'); process.exit(1) }
160
- const dest = rest.find(a => a !== root && !a.startsWith('--'))
161
- if (!dest) { console.error(HELP); process.exit(1) }
469
+ const dest = parsed.positional[0]!
162
470
  mkdirSync(dirname(dest), { recursive: true })
163
471
  ledger.forkWhatIf(dest)
164
472
  console.log(`what-if 分叉已生成:${dest}(预演在副本,正本零改动)`)
165
473
  break
166
474
  }
167
475
  case 'pw-list': {
168
- const root = rootOf(rest)
169
- const tenant = tenantOf(rest)
170
476
  const ledger = openLedgerIfExists(root, tenant)
171
477
  if (!ledger) { console.log('(无账本)'); break }
172
478
  for (const w of ledger.listPendingWrites()) {
@@ -177,11 +483,8 @@ switch (cmd) {
177
483
  case 'pw-request':
178
484
  case 'pw-confirm':
179
485
  case 'pw-compensate': {
180
- const root = rootOf(rest)
181
- const tenant = tenantOf(rest)
182
486
  const ledger = ensureLedger(root, tenant)
183
- const positional = rest.filter(a => a !== root && !a.startsWith('--'))
184
- if (positional.length < (cmd === 'pw-request' ? 3 : 2)) { console.error(HELP); process.exit(1) }
487
+ const positional = parsed.positional
185
488
  if (cmd === 'pw-request') {
186
489
  const r = ledger.requestPendingWrite({ idemKey: positional[0]!, seam: positional[1]!, payload: JSON.parse(positional[2]!) as unknown })
187
490
  console.log(`登记:${JSON.stringify(r)}(L2:只登记不执行)`)
@@ -194,6 +497,70 @@ switch (cmd) {
194
497
  }
195
498
  break
196
499
  }
500
+ case 'repair-plan': {
501
+ // 全程只读:不用 ensureLedger/openLedgerIfExists(它们会建库、迁移 v1 schema、
502
+ // 重建投影);planLedgerRepair 把 db 复制到临时目录再只读读取,正本一次都不打开。
503
+ if (!ledgerExists(root)) { console.error(`无账本(未迁移 root):${ledgerDbPath(root)}`); process.exit(1) }
504
+ let mappings: unknown
505
+ if (parsed.mapping !== undefined) {
506
+ try {
507
+ mappings = loadEvidenceMappingFile(parsed.mapping)
508
+ } catch (e) {
509
+ // fail-closed:映射不可读/非法 JSON 一律拒绝,绝不退化成「无映射照跑」
510
+ console.error(e instanceof Error ? e.message : String(e))
511
+ process.exit(1)
512
+ }
513
+ }
514
+ try {
515
+ const plan = planLedgerRepair({ stateRoot: root, sourceTenant: tenant, mappings })
516
+ console.log(parsed.format === 'json' ? JSON.stringify(plan, null, 2) : formatRepairPlan(plan))
517
+ } catch (e) {
518
+ // source db/WAL/SHM changed during the read: never present a mixed-time plan.
519
+ console.error(e instanceof Error ? e.message : String(e))
520
+ process.exit(1)
521
+ }
522
+ break
523
+ }
524
+ case 'repair-apply': {
525
+ if (!ledgerExists(root)) { console.error(`无账本(未迁移 root):${ledgerDbPath(root)}`); process.exit(1) }
526
+ let mappings: unknown
527
+ try {
528
+ mappings = loadEvidenceMappingFile(parsed.mapping!)
529
+ } catch (e) {
530
+ console.error(e instanceof Error ? e.message : String(e))
531
+ process.exit(1)
532
+ }
533
+ try {
534
+ const result = applyLedgerRepair({
535
+ stateRoot: root,
536
+ sourceTenant: tenant,
537
+ mappings,
538
+ planDigest: parsed.planDigest!,
539
+ authorizeApply: true,
540
+ receiptOut: parsed.receiptOut,
541
+ })
542
+ console.log(parsed.format === 'json' ? JSON.stringify(result, null, 2) : formatRepairApplyResult(result))
543
+ } catch (e) {
544
+ console.error(e instanceof Error ? e.message : String(e))
545
+ process.exit(1)
546
+ }
547
+ break
548
+ }
549
+ case 'repair-rollback': {
550
+ try {
551
+ const r = restoreRepairBackup({
552
+ stateRoot: root,
553
+ backupDir: parsed.backup!,
554
+ authorizeRollback: true,
555
+ })
556
+ console.log(`已从校验过的 backup 还原:${r.restoredFrom}`)
557
+ console.log(`plan-digest=${r.manifest.planDigest} mapping-sha256=${r.manifest.mappingSha256}`)
558
+ } catch (e) {
559
+ console.error(e instanceof Error ? e.message : String(e))
560
+ process.exit(1)
561
+ }
562
+ break
563
+ }
197
564
  default:
198
565
  console.error(cmd ? `未知命令 ${cmd}` : HELP)
199
566
  console.error(HELP)