@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
@@ -0,0 +1,538 @@
1
+ /**
2
+ * 账本 tenant 归属修复:授权 apply / backup / rollback / 回执(issue #254 执行面)。
3
+ *
4
+ * 硬纪律:
5
+ * A. 三重门:显式 `--i-authorize-apply` + 匹配的 `planDigest` + 可 apply 的证据映射计划。
6
+ * B. 写前强制 backup(db + -wal/-shm + manifest 校验和);损坏 backup fail-closed。
7
+ * C. 原地 UPDATE 保留 seq;CAS `WHERE seq=? AND tenant_id=?`;整批单一事务。
8
+ * D. 成功后对受影响 tenant 调用既有 rebuildProjections;不做启发式归属。
9
+ * E. 幂等:若映射目标已全部就位且存在同 digest 的 applied stamp,重跑成功且无额外变更。
10
+ * F. 本模块默认不触碰 founder 真账本;真实 repair 另走 owner gate + 私有回执。
11
+ */
12
+ import Database from 'better-sqlite3';
13
+ import { createHash } from 'node:crypto';
14
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ import { ledgerDbPath, ledgerExists, openDb } from './state-ledger.js';
17
+ import { parseEvidenceMappings, planLedgerRepair, } from './ledger-repair-plan.js';
18
+ export const REPAIR_BACKUP_SCHEMA = 'gotry.ledger-repair-backup/1';
19
+ export const REPAIR_APPLY_SCHEMA = 'gotry.ledger-repair-apply/1';
20
+ export const REPAIR_RECEIPT_SCHEMA = 'gotry.ledger-repair-receipt/1';
21
+ export const REPAIR_APPLIED_STAMP_SCHEMA = 'gotry.ledger-repair-applied/1';
22
+ const DB_SIDECARS = ['', '-wal', '-shm'];
23
+ export class RepairApplyError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = 'RepairApplyError';
28
+ this.code = code;
29
+ }
30
+ }
31
+ function sha256Bytes(bytes) {
32
+ return createHash('sha256').update(bytes).digest('hex');
33
+ }
34
+ function sha256File(path) {
35
+ const bytes = readFileSync(path);
36
+ return { sha256: sha256Bytes(bytes), bytes: bytes.length };
37
+ }
38
+ function fileDigestOrMissing(path) {
39
+ if (!existsSync(path))
40
+ return { present: false, sha256: null, bytes: null };
41
+ const d = sha256File(path);
42
+ return { present: true, sha256: d.sha256, bytes: d.bytes };
43
+ }
44
+ function sourceComponentDigest(path) {
45
+ const d = fileDigestOrMissing(path);
46
+ if (!d.present)
47
+ return 'missing';
48
+ return `present:${d.bytes}:${d.sha256}`;
49
+ }
50
+ function sourceGroupDigest(dbPath) {
51
+ return DB_SIDECARS.map(suffix => `${suffix}\0${sourceComponentDigest(dbPath + suffix)}`).join('\n');
52
+ }
53
+ export function mappingContentSha256(mappings) {
54
+ return sha256Bytes(JSON.stringify(mappings));
55
+ }
56
+ export function redactedPath(path) {
57
+ return sha256Bytes(path).slice(0, 16);
58
+ }
59
+ export function stateRootFingerprint(stateRoot) {
60
+ const dbPath = ledgerDbPath(stateRoot);
61
+ return sha256Bytes(sourceGroupDigest(dbPath)).slice(0, 32);
62
+ }
63
+ function tryGitHead() {
64
+ try {
65
+ // Lazy require via spawn would be heavy; allow env override for tests / CI.
66
+ if (process.env['GOTRY_REPAIR_GIT_HEAD'])
67
+ return process.env['GOTRY_REPAIR_GIT_HEAD'];
68
+ return null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ function backupRoot(stateRoot) {
75
+ return join(stateRoot, '.gotry-repair-backup');
76
+ }
77
+ function stampDir(stateRoot) {
78
+ return join(stateRoot, 'gotry-state', '.gotry-repair-applied');
79
+ }
80
+ export function stampPath(stateRoot, planDigest) {
81
+ return join(stampDir(stateRoot), `${planDigest}.json`);
82
+ }
83
+ function isoStamp() {
84
+ return new Date().toISOString().replace(/[:.]/g, '-');
85
+ }
86
+ function atomicWriteJson(path, value) {
87
+ mkdirSync(dirname(path), { recursive: true });
88
+ const tmp = `${path}.tmp`;
89
+ writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf-8');
90
+ renameSync(tmp, path);
91
+ }
92
+ function readJsonFile(path) {
93
+ return JSON.parse(readFileSync(path, 'utf-8'));
94
+ }
95
+ function censusFromDb(db) {
96
+ const hasEvents = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='events'").get();
97
+ if (!hasEvents)
98
+ return [];
99
+ const cols = db.prepare('PRAGMA table_info(events)').all();
100
+ if (!cols.some(c => c.name === 'tenant_id')) {
101
+ const n = db.prepare('SELECT COUNT(*) AS n FROM events').get().n;
102
+ return n ? [{ tenant: 'local', events: n }] : [];
103
+ }
104
+ return db.prepare(`SELECT tenant_id AS tenant, COUNT(*) AS events FROM events GROUP BY tenant_id ORDER BY tenant_id`).all();
105
+ }
106
+ function openWritableDb(stateRoot) {
107
+ const path = ledgerDbPath(stateRoot);
108
+ if (!existsSync(path))
109
+ throw new RepairApplyError('ledger_missing', `账本不存在:${path}`);
110
+ const db = new Database(path);
111
+ db.pragma('journal_mode = WAL');
112
+ db.pragma('synchronous = FULL');
113
+ db.pragma('busy_timeout = 5000');
114
+ return db;
115
+ }
116
+ /** Checkpoint WAL into the main db so backup/restore is a coherent snapshot. */
117
+ function checkpointWal(db) {
118
+ db.pragma('wal_checkpoint(FULL)');
119
+ }
120
+ export function createRepairBackup(opts) {
121
+ const dbPath = ledgerDbPath(opts.stateRoot);
122
+ if (!ledgerExists(opts.stateRoot)) {
123
+ throw new RepairApplyError('ledger_missing', `账本不存在:${dbPath}`);
124
+ }
125
+ // Open briefly to checkpoint, then close before copying.
126
+ {
127
+ const db = openWritableDb(opts.stateRoot);
128
+ try {
129
+ checkpointWal(db);
130
+ }
131
+ finally {
132
+ db.close();
133
+ }
134
+ }
135
+ const before = sourceGroupDigest(dbPath);
136
+ const backupDir = join(backupRoot(opts.stateRoot), `${isoStamp()}-${opts.planDigest}`);
137
+ mkdirSync(backupDir, { recursive: true });
138
+ const files = [];
139
+ for (const suffix of DB_SIDECARS) {
140
+ const src = dbPath + suffix;
141
+ const dest = join(backupDir, `gotry-state.db${suffix}`);
142
+ if (!existsSync(src)) {
143
+ files.push({ suffix, present: false, sha256: null, bytes: null });
144
+ continue;
145
+ }
146
+ copyFileSync(src, dest);
147
+ const d = sha256File(dest);
148
+ files.push({ suffix, present: true, sha256: d.sha256, bytes: d.bytes });
149
+ }
150
+ if (sourceGroupDigest(dbPath) !== before) {
151
+ rmSync(backupDir, { recursive: true, force: true });
152
+ throw new RepairApplyError('source_changed_during_backup', '备份期间正本发生变化,已拒绝继续');
153
+ }
154
+ const manifest = {
155
+ schema: REPAIR_BACKUP_SCHEMA,
156
+ planDigest: opts.planDigest,
157
+ mappingSha256: opts.mappingSha256,
158
+ createdAt: new Date().toISOString(),
159
+ sourceTenant: opts.sourceTenant,
160
+ tenantCensusBefore: opts.tenantCensusBefore,
161
+ files,
162
+ gitHead: tryGitHead(),
163
+ };
164
+ atomicWriteJson(join(backupDir, 'manifest.json'), manifest);
165
+ return { backupDir, manifest };
166
+ }
167
+ export function loadBackupManifest(backupDir) {
168
+ const path = join(backupDir, 'manifest.json');
169
+ if (!existsSync(path))
170
+ throw new RepairApplyError('backup_manifest_missing', `缺少 manifest:${path}`);
171
+ const manifest = readJsonFile(path);
172
+ if (manifest.schema !== REPAIR_BACKUP_SCHEMA) {
173
+ throw new RepairApplyError('backup_schema_invalid', `未知 backup schema:${String(manifest.schema)}`);
174
+ }
175
+ return manifest;
176
+ }
177
+ /** Verify backup files match manifest checksums. */
178
+ export function validateRepairBackup(backupDir) {
179
+ const manifest = loadBackupManifest(backupDir);
180
+ for (const f of manifest.files) {
181
+ const path = join(backupDir, `gotry-state.db${f.suffix}`);
182
+ if (!f.present) {
183
+ if (existsSync(path)) {
184
+ throw new RepairApplyError('backup_corrupt', `manifest 标记缺失但文件存在:${path}`);
185
+ }
186
+ continue;
187
+ }
188
+ if (!existsSync(path)) {
189
+ throw new RepairApplyError('backup_corrupt', `backup 文件缺失:${path}`);
190
+ }
191
+ const d = sha256File(path);
192
+ if (d.sha256 !== f.sha256 || d.bytes !== f.bytes) {
193
+ throw new RepairApplyError('backup_checksum_mismatch', `backup 校验和不匹配:${path}`);
194
+ }
195
+ }
196
+ return manifest;
197
+ }
198
+ export function restoreRepairBackup(opts) {
199
+ if (opts.authorizeRollback !== true) {
200
+ throw new RepairApplyError('rollback_not_authorized', '缺少显式 rollback 授权');
201
+ }
202
+ const manifest = validateRepairBackup(opts.backupDir);
203
+ const dbPath = ledgerDbPath(opts.stateRoot);
204
+ mkdirSync(dirname(dbPath), { recursive: true });
205
+ // Replace live db files. Callers must not hold open SQLite handles on this root.
206
+ for (const suffix of DB_SIDECARS) {
207
+ const live = dbPath + suffix;
208
+ if (existsSync(live))
209
+ rmSync(live);
210
+ }
211
+ for (const f of manifest.files) {
212
+ if (!f.present)
213
+ continue;
214
+ const src = join(opts.backupDir, `gotry-state.db${f.suffix}`);
215
+ copyFileSync(src, dbPath + f.suffix);
216
+ }
217
+ // Re-validate live files against manifest.
218
+ for (const f of manifest.files) {
219
+ const live = dbPath + f.suffix;
220
+ if (!f.present) {
221
+ if (existsSync(live)) {
222
+ throw new RepairApplyError('rollback_verify_failed', `还原后出现不应存在的 sidecar:${live}`);
223
+ }
224
+ continue;
225
+ }
226
+ const d = sha256File(live);
227
+ if (d.sha256 !== f.sha256) {
228
+ throw new RepairApplyError('rollback_verify_failed', `还原后校验和与 backup 不一致:${live}`);
229
+ }
230
+ }
231
+ return { restoredFrom: opts.backupDir, manifest };
232
+ }
233
+ function loadAppliedStamp(stateRoot, planDigest) {
234
+ const path = stampPath(stateRoot, planDigest);
235
+ if (!existsSync(path))
236
+ return null;
237
+ const stamp = readJsonFile(path);
238
+ if (stamp.schema !== REPAIR_APPLIED_STAMP_SCHEMA)
239
+ return null;
240
+ if (stamp.planDigest !== planDigest)
241
+ return null;
242
+ return stamp;
243
+ }
244
+ function eventRow(db, seq) {
245
+ return db.prepare(`SELECT seq, tenant_id, kind, subject_id, idem_key FROM events WHERE seq = ?`).get(seq);
246
+ }
247
+ function verifyAlreadyApplied(db, mappings, planMoves) {
248
+ const bySeq = new Map(planMoves.map(m => [m.seq, m]));
249
+ const records = [];
250
+ for (const m of mappings) {
251
+ const planned = bySeq.get(m.seq);
252
+ const row = eventRow(db, m.seq);
253
+ if (!row)
254
+ return null;
255
+ if (row.tenant_id !== m.toTenant)
256
+ return null;
257
+ records.push({
258
+ seq: m.seq,
259
+ beforeTenant: m.fromTenant,
260
+ afterTenant: m.toTenant,
261
+ status: 'already_applied',
262
+ kind: planned?.kind ?? row.kind,
263
+ subjectId: planned?.subjectId ?? row.subject_id,
264
+ idemKey: planned?.idemKey ?? row.idem_key,
265
+ evidence: m.evidence,
266
+ });
267
+ }
268
+ return records;
269
+ }
270
+ function buildReceipt(opts) {
271
+ return {
272
+ schema: REPAIR_RECEIPT_SCHEMA,
273
+ planDigest: opts.planDigest,
274
+ mappingSha256: opts.mappingSha256,
275
+ authorizedBy: opts.authorizedBy,
276
+ executedAt: new Date().toISOString(),
277
+ gitHead: tryGitHead(),
278
+ stateRootFingerprint: stateRootFingerprint(opts.stateRoot),
279
+ stateRootRedacted: redactedPath(opts.stateRoot),
280
+ backupDirRedacted: redactedPath(opts.backupDir),
281
+ sourceTenant: opts.sourceTenant,
282
+ tenantCensusBefore: opts.before,
283
+ tenantCensusAfter: opts.after,
284
+ affectedSeqs: opts.moves.map(m => m.seq),
285
+ moves: opts.moves,
286
+ mode: opts.mode,
287
+ notes: opts.notes,
288
+ };
289
+ }
290
+ export function applyLedgerRepair(opts) {
291
+ if (opts.authorizeApply !== true) {
292
+ throw new RepairApplyError('apply_not_authorized', '缺少显式 apply 授权');
293
+ }
294
+ if (!/^[0-9a-f]{16}$/.test(opts.planDigest)) {
295
+ throw new RepairApplyError('plan_digest_invalid', `planDigest 必须是 16 位小写 hex:${opts.planDigest}`);
296
+ }
297
+ if (!ledgerExists(opts.stateRoot)) {
298
+ throw new RepairApplyError('ledger_missing', `账本不存在:${ledgerDbPath(opts.stateRoot)}`);
299
+ }
300
+ const sourceTenant = opts.sourceTenant ?? 'local';
301
+ const mappingSha = mappingContentSha256(opts.mappings);
302
+ const authorizedBy = opts.authorizedBy ?? 'cli:i-authorize-apply';
303
+ const plan = planLedgerRepair({
304
+ stateRoot: opts.stateRoot,
305
+ sourceTenant,
306
+ mappings: opts.mappings,
307
+ });
308
+ const parsed = parseEvidenceMappings(opts.mappings, sourceTenant);
309
+ const intendedMoves = parsed.valid.filter(m => m.toTenant !== m.fromTenant);
310
+ // Path A: fresh applicable plan with matching digest.
311
+ if (plan.planDigest === opts.planDigest && plan.applyable) {
312
+ return executeMoves({
313
+ stateRoot: opts.stateRoot,
314
+ sourceTenant,
315
+ plan,
316
+ mappingSha,
317
+ authorizedBy,
318
+ receiptOut: opts.receiptOut,
319
+ hooks: opts.hooks,
320
+ });
321
+ }
322
+ // Path B: idempotent already-applied (stamp + live rows at destinations).
323
+ const stamp = loadAppliedStamp(opts.stateRoot, opts.planDigest);
324
+ if (stamp && stamp.mappingSha256 === mappingSha) {
325
+ const db = openWritableDb(opts.stateRoot);
326
+ try {
327
+ const records = verifyAlreadyApplied(db, intendedMoves.length ? intendedMoves : stamp.moves.map(m => ({
328
+ seq: m.seq,
329
+ fromTenant: m.beforeTenant,
330
+ toTenant: m.afterTenant,
331
+ evidence: m.evidence ?? 'stamp',
332
+ })), plan.entries.filter(e => e.decision === 'move' || e.decision === 'reject'));
333
+ if (records) {
334
+ const after = censusFromDb(db);
335
+ const notes = [
336
+ '幂等重跑:applied stamp 与目标 tenant 行均已就位,未做额外写入。',
337
+ 'fixture 绿不等于真实 founder repair 已执行。',
338
+ ];
339
+ const receipt = buildReceipt({
340
+ planDigest: opts.planDigest,
341
+ mappingSha256: mappingSha,
342
+ authorizedBy,
343
+ stateRoot: opts.stateRoot,
344
+ backupDir: stamp.backupDir,
345
+ sourceTenant,
346
+ before: stamp.tenantCensusAfter,
347
+ after,
348
+ moves: records,
349
+ mode: 'already_applied',
350
+ notes,
351
+ });
352
+ if (opts.receiptOut)
353
+ atomicWriteJson(opts.receiptOut, receipt);
354
+ return {
355
+ schema: REPAIR_APPLY_SCHEMA,
356
+ applyAuthorized: true,
357
+ mode: 'already_applied',
358
+ planDigest: opts.planDigest,
359
+ mappingSha256: mappingSha,
360
+ backupDir: stamp.backupDir,
361
+ sourceTenant,
362
+ tenantCensusBefore: stamp.tenantCensusAfter,
363
+ tenantCensusAfter: after,
364
+ moves: records,
365
+ rebuiltTenants: [],
366
+ receipt,
367
+ notes,
368
+ };
369
+ }
370
+ }
371
+ finally {
372
+ db.close();
373
+ }
374
+ }
375
+ if (plan.planDigest !== opts.planDigest) {
376
+ throw new RepairApplyError('plan_digest_mismatch', `planDigest 不匹配:expected=${opts.planDigest} actual=${plan.planDigest}`);
377
+ }
378
+ throw new RepairApplyError('plan_not_applyable', `计划不可 apply(applyable=${plan.applyable}, move=${plan.totals.move}, reject=${plan.totals.reject})`);
379
+ }
380
+ function executeMoves(opts) {
381
+ const moves = opts.plan.entries.filter(e => e.decision === 'move');
382
+ if (moves.length === 0) {
383
+ throw new RepairApplyError('plan_not_applyable', '计划无 move 条目');
384
+ }
385
+ const { backupDir } = createRepairBackup({
386
+ stateRoot: opts.stateRoot,
387
+ planDigest: opts.plan.planDigest,
388
+ mappingSha256: opts.mappingSha,
389
+ sourceTenant: opts.sourceTenant,
390
+ tenantCensusBefore: opts.plan.tenantCensusBefore,
391
+ });
392
+ validateRepairBackup(backupDir);
393
+ const db = openWritableDb(opts.stateRoot);
394
+ const records = [];
395
+ const injectAfter = opts.hooks?.injectFailureAfterMoves;
396
+ try {
397
+ const run = db.transaction(() => {
398
+ const update = db.prepare(`UPDATE events SET tenant_id = ? WHERE seq = ? AND tenant_id = ?`);
399
+ let movedCount = 0;
400
+ for (const m of moves) {
401
+ const row = eventRow(db, m.seq);
402
+ if (!row) {
403
+ throw new RepairApplyError('cas_miss', `seq ${m.seq} 不存在`);
404
+ }
405
+ if (row.tenant_id === m.afterTenant) {
406
+ records.push({
407
+ seq: m.seq,
408
+ beforeTenant: m.beforeTenant,
409
+ afterTenant: m.afterTenant,
410
+ status: 'already_applied',
411
+ kind: m.kind,
412
+ subjectId: m.subjectId,
413
+ idemKey: m.idemKey,
414
+ evidence: m.evidence,
415
+ });
416
+ continue;
417
+ }
418
+ if (row.tenant_id !== m.beforeTenant) {
419
+ throw new RepairApplyError('cas_miss', `seq ${m.seq} 当前 tenant=${row.tenant_id},期望 before=${m.beforeTenant}`);
420
+ }
421
+ const info = update.run(m.afterTenant, m.seq, m.beforeTenant);
422
+ if (info.changes !== 1) {
423
+ throw new RepairApplyError('cas_miss', `seq ${m.seq} CAS UPDATE 未命中`);
424
+ }
425
+ movedCount++;
426
+ records.push({
427
+ seq: m.seq,
428
+ beforeTenant: m.beforeTenant,
429
+ afterTenant: m.afterTenant,
430
+ status: 'moved',
431
+ kind: m.kind,
432
+ subjectId: m.subjectId,
433
+ idemKey: m.idemKey,
434
+ evidence: m.evidence,
435
+ });
436
+ if (injectAfter !== undefined && movedCount >= injectAfter) {
437
+ throw new RepairApplyError('injected_failure', `测试注入失败:已成功 ${movedCount} 条后中止`);
438
+ }
439
+ }
440
+ });
441
+ run();
442
+ checkpointWal(db);
443
+ }
444
+ catch (error) {
445
+ db.close();
446
+ throw error;
447
+ }
448
+ db.close();
449
+ // Rebuild projections for every affected tenant via the normal ledger API.
450
+ const tenants = [...new Set(records.flatMap(r => [r.beforeTenant, r.afterTenant]))].sort();
451
+ for (const tenant of tenants) {
452
+ const led = openDb(opts.stateRoot, tenant);
453
+ led.rebuildProjections();
454
+ led.close();
455
+ }
456
+ const verifyDb = openWritableDb(opts.stateRoot);
457
+ let after;
458
+ try {
459
+ after = censusFromDb(verifyDb);
460
+ for (const r of records) {
461
+ const row = eventRow(verifyDb, r.seq);
462
+ if (!row || row.tenant_id !== r.afterTenant) {
463
+ throw new RepairApplyError('post_apply_verify_failed', `seq ${r.seq} 未落到 ${r.afterTenant}`);
464
+ }
465
+ }
466
+ }
467
+ finally {
468
+ verifyDb.close();
469
+ }
470
+ const notes = [
471
+ '已通过显式授权门执行 apply;事件 seq 保留,仅更新 tenant_id。',
472
+ '已对受影响 tenant 重建投影。',
473
+ 'fixture/隔离 stateRoot 证据不等于真实 founder repair。',
474
+ ];
475
+ const stamp = {
476
+ schema: REPAIR_APPLIED_STAMP_SCHEMA,
477
+ planDigest: opts.plan.planDigest,
478
+ mappingSha256: opts.mappingSha,
479
+ appliedAt: new Date().toISOString(),
480
+ sourceTenant: opts.sourceTenant,
481
+ moves: records,
482
+ tenantCensusAfter: after,
483
+ backupDir,
484
+ };
485
+ atomicWriteJson(stampPath(opts.stateRoot, opts.plan.planDigest), stamp);
486
+ const receipt = buildReceipt({
487
+ planDigest: opts.plan.planDigest,
488
+ mappingSha256: opts.mappingSha,
489
+ authorizedBy: opts.authorizedBy,
490
+ stateRoot: opts.stateRoot,
491
+ backupDir,
492
+ sourceTenant: opts.sourceTenant,
493
+ before: opts.plan.tenantCensusBefore,
494
+ after,
495
+ moves: records,
496
+ mode: 'applied',
497
+ notes,
498
+ });
499
+ if (opts.receiptOut)
500
+ atomicWriteJson(opts.receiptOut, receipt);
501
+ return {
502
+ schema: REPAIR_APPLY_SCHEMA,
503
+ applyAuthorized: true,
504
+ mode: 'applied',
505
+ planDigest: opts.plan.planDigest,
506
+ mappingSha256: opts.mappingSha,
507
+ backupDir,
508
+ sourceTenant: opts.sourceTenant,
509
+ tenantCensusBefore: opts.plan.tenantCensusBefore,
510
+ tenantCensusAfter: after,
511
+ moves: records,
512
+ rebuiltTenants: tenants,
513
+ receipt,
514
+ notes,
515
+ };
516
+ }
517
+ export function formatRepairApplyResult(result) {
518
+ const lines = [];
519
+ lines.push(`账本 tenant 修复 apply(${result.mode})`);
520
+ lines.push(`plan-digest=${result.planDigest}`);
521
+ lines.push(`mapping-sha256=${result.mappingSha256}`);
522
+ lines.push(`backup=${result.backupDir}`);
523
+ lines.push(`源租户 scope: ${result.sourceTenant}`);
524
+ lines.push(`census before: ${result.tenantCensusBefore.map(t => `${t.tenant}=${t.events}`).join(' ') || '(空)'}`);
525
+ lines.push(`census after : ${result.tenantCensusAfter.map(t => `${t.tenant}=${t.events}`).join(' ') || '(空)'}`);
526
+ lines.push(`rebuilt tenants: ${result.rebuiltTenants.join(',') || '(none)'}`);
527
+ lines.push('');
528
+ lines.push(` seq status before -> after`);
529
+ for (const m of result.moves) {
530
+ lines.push(`${String(m.seq).padStart(5)} ${m.status.padEnd(16)} ${m.beforeTenant} -> ${m.afterTenant}`);
531
+ }
532
+ lines.push('');
533
+ for (const n of result.notes)
534
+ lines.push(`* ${n}`);
535
+ lines.push(`applyAuthorized=${result.applyAuthorized}`);
536
+ lines.push(`receipt-schema=${result.receipt.schema}`);
537
+ return lines.join('\n');
538
+ }