@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.27

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 (507) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1744 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +135 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +546 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +509 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -1,166 +1,111 @@
1
+ /**
2
+ * 账本回归(ADR-15 执行锚点,run-all §28):
3
+ * 1 事务原子性:事务内注入异常 → 整体回滚,账本无痕
4
+ * 2 红线进事务:conditions 缺失拒绝且零事件落账
5
+ * 3 幂等物理化:愿望同名/效用语义键/行程 trip_id 重放 no-op;wish_id 语义派生稳定
6
+ * 4 守门随账本:权重变更守门/词表外日期拒收/重叠冲突即停/负面清单拒收
7
+ * 5 confirm-outcome 单事务:效用+行程同生(跨文件分叉不可能)
8
+ * 6 fold 重建:DROP 投影 → 重放 → 与直读一致(账本/投影永不分叉)
9
+ * 7 rewind:fold 截到历史 seq → 投影回到历史时点;无参 rebuild 回最新
10
+ * 8 one-shot 迁移:旧 JSON/JSONL → events;快照先行;重复 ensure 不重复导入
11
+ * 9 durable 工单崩溃恢复:子进程 settle 前 exit 9 → 恢复零重算(exactly-once)
12
+ * 10 pending_writes saga:幂等键去重/L3 确认一次/补偿/审计事件链/what-if 分叉隔离
13
+ * 11 ADR-16 tenant scope:append/read/fold/rebuild/迁移边界/同 id/跨进程 reopen 全隔离;
14
+ * 精确 fold 反例、kind+limit 截断、workflow/forget 行归属都必须可负向验红
15
+ * 运行(在 ts/ 下):npx tsx scripts/ledger-tests.ts
16
+ */
1
17
  import Database from 'better-sqlite3';
2
18
  import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
3
19
  import { tmpdir } from 'node:os';
4
20
  import { join } from 'node:path';
5
21
  import { spawnSync } from 'node:child_process';
6
- import { ensureLedger, openLedgerIfExists } from '../src/state-ledger.js';
22
+ import { persistAsyncTicket } from '../src/loop.js';
23
+ import { ensureLedger, ledgerDbPath, ledgerExists, openLedgerIfExists, readWishPoolWithFallback } from '../src/state-ledger.js';
24
+ import { parseFlightPackToSpec } from '../src/unified.js';
7
25
  let pass = 0;
8
26
  let fail = 0;
9
27
  function assert(cond, msg) {
10
28
  if (cond) {
11
29
  pass++;
12
30
  console.log(` ok - ${msg}`);
13
- } else {
31
+ }
32
+ else {
14
33
  fail++;
15
34
  console.error(` FAIL - ${msg}`);
16
35
  }
17
36
  }
37
+ function terminalOutcomeOf(stdout) {
38
+ const line = stdout.trim().split('\n').filter(Boolean).at(-1);
39
+ if (!line)
40
+ return null;
41
+ try {
42
+ const value = JSON.parse(line);
43
+ if (value.schema !== 'gotry_async_terminal.v1'
44
+ || typeof value.ticket_id !== 'string'
45
+ || (value.status !== 'succeeded' && value.status !== 'failed')
46
+ || typeof value.passed !== 'number'
47
+ || typeof value.total !== 'number'
48
+ || typeof value.checks !== 'object'
49
+ || !Array.isArray(value.failed_checks))
50
+ return null;
51
+ return value;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ // ---- 1-7:fresh root 基座面 ------------------------------------------------------
18
58
  const root = mkdtempSync(join(tmpdir(), 'gotry-ledger-'));
19
59
  const ledger = ensureLedger(root);
20
60
  const r1 = ledger.appendMotivationPatch({
21
- weights: {
22
- escape_rest: 0.7
23
- },
24
- evidence: [
25
- '用户原话:想去湖边什么都不干'
26
- ],
27
- hard: {
28
- wake_not_before: '06:30'
29
- }
61
+ weights: { escape_rest: 0.7 }, evidence: ['用户原话:想去湖边什么都不干'], hard: { wake_not_before: '06:30' },
30
62
  });
31
63
  assert(r1.saved === true, '画像补丁落账(saved)');
32
- const r2 = ledger.appendMotivationPatch({
33
- evidence: [
34
- '用户原话:想去湖边什么都不干'
35
- ]
36
- });
64
+ const r2 = ledger.appendMotivationPatch({ evidence: ['用户原话:想去湖边什么都不干'] });
37
65
  assert(r2.saved === false, '同 evidence 重放幂等(mergeProfile 守门 → 零事件)');
38
66
  const before = ledger.countEvents();
39
67
  let threw = false;
40
68
  try {
41
- ledger.db.transaction(()=>{
42
- ledger.insertEvent({
43
- actor: 'test',
44
- kind: 'probe',
45
- payload: {
46
- x: 1
47
- }
48
- });
69
+ ledger.db.transaction(() => {
70
+ ledger.insertEvent({ actor: 'test', kind: 'probe', payload: { x: 1 } });
49
71
  throw new Error('boom');
50
72
  })();
51
- } catch {
73
+ }
74
+ catch {
52
75
  threw = true;
53
76
  }
54
77
  assert(threw && ledger.countEvents() === before, '事务内异常 → 整体回滚,账本无痕(崩溃一致性的单事务证明)');
55
78
  threw = false;
56
79
  try {
57
- ledger.appendWish({
58
- name: '无条件憧憬',
59
- reason: '',
60
- conditions: undefined
61
- });
62
- } catch {
80
+ ledger.appendWish({ name: '无条件憧憬', reason: '', conditions: undefined });
81
+ }
82
+ catch {
63
83
  threw = true;
64
84
  }
65
85
  assert(threw && ledger.countEvents() === before, 'conditions 红线在事务内拒绝且零事件(红线进 schema 层)');
66
- const w1 = ledger.appendWish({
67
- name: '大理·洱海',
68
- reason: '',
69
- conditions: {
70
- days: 5,
71
- budget_cny: 4950,
72
- best_months: [
73
- 3,
74
- 4
75
- ]
76
- }
77
- });
78
- const w2 = ledger.appendWish({
79
- name: '大理·洱海',
80
- reason: '两周年想再去',
81
- conditions: {
82
- days: 6
83
- }
84
- });
86
+ const w1 = ledger.appendWish({ name: '大理·洱海', reason: '', conditions: { days: 5, budget_cny: 4950, best_months: [3, 4] } });
87
+ const w2 = ledger.appendWish({ name: '大理·洱海', reason: '两周年想再去', conditions: { days: 6 } });
85
88
  assert(w1.added === true && w2.added === false && w1.wish_id === w2.wish_id, '同名愿望幂等更新,wish_id 语义派生稳定');
86
89
  assert(ledger.readWishPool().length === 1, '愿望池投影恰 1 条');
87
- const u1 = ledger.appendUtilityEvent({
88
- wish_id: w1.wish_id,
89
- kind: 'recalled',
90
- ts: '2026-08-28T00:00:00Z',
91
- ctx: 'test'
92
- });
93
- const u2 = ledger.appendUtilityEvent({
94
- wish_id: w1.wish_id,
95
- kind: 'recalled',
96
- ts: '2026-08-28T00:00:00Z',
97
- ctx: 'test'
98
- });
90
+ const u1 = ledger.appendUtilityEvent({ wish_id: w1.wish_id, kind: 'recalled', ts: '2026-08-28T00:00:00Z', ctx: 'test' });
91
+ const u2 = ledger.appendUtilityEvent({ wish_id: w1.wish_id, kind: 'recalled', ts: '2026-08-28T00:00:00Z', ctx: 'test' });
99
92
  assert(u1.appended === true && u2.appended === false, '效用事件语义键幂等(重放 no-op)');
100
- ledger.appendUtilityEvent({
101
- wish_id: w1.wish_id,
102
- kind: 'verified_outcome',
103
- ts: '2026-08-28T00:00:00Z',
104
- ctx: 'test-noattr'
105
- });
93
+ ledger.appendUtilityEvent({ wish_id: w1.wish_id, kind: 'verified_outcome', ts: '2026-08-28T00:00:00Z', ctx: 'test-noattr' });
106
94
  assert(ledger.readUtilityEvents().at(-1)?.kind === 'applied', '无归因 verified 降级 applied(六件语义纪律随账本)');
107
- const t1 = ledger.appendTripEvent({
108
- destination: '大理',
109
- start: '2026-10-01',
110
- source: 'user-verbatim',
111
- evidence: '用户原话:国庆去了大理'
112
- });
113
- const t1dup = ledger.appendTripEvent({
114
- destination: '大理',
115
- start: '2026-10-01',
116
- source: 'user-verbatim',
117
- evidence: '用户原话:国庆去了大理'
118
- });
95
+ const t1 = ledger.appendTripEvent({ destination: '大理', start: '2026-10-01', source: 'user-verbatim', evidence: '用户原话:国庆去了大理' });
96
+ const t1dup = ledger.appendTripEvent({ destination: '大理', start: '2026-10-01', source: 'user-verbatim', evidence: '用户原话:国庆去了大理' });
119
97
  assert(t1.appended === true && t1dup.appended === false, '行程 trip_id 幂等');
120
- const tOverlap = ledger.appendTripEvent({
121
- destination: '大理',
122
- start: '2026-10-03',
123
- source: 'user-verbatim',
124
- evidence: '重叠探针'
125
- });
98
+ const tOverlap = ledger.appendTripEvent({ destination: '大理', start: '2026-10-03', source: 'user-verbatim', evidence: '重叠探针' });
126
99
  assert(tOverlap.appended === false && /重叠/.test(tOverlap.reason ?? ''), '同目的地日期重叠冲突即停(由人裁决)');
127
- const tBad = ledger.appendTripEvent({
128
- destination: 'X地',
129
- start: '十月一',
130
- source: 'user-verbatim',
131
- evidence: 'x'
132
- });
100
+ const tBad = ledger.appendTripEvent({ destination: 'X地', start: '十月一', source: 'user-verbatim', evidence: 'x' });
133
101
  assert(tBad.appended === false, '词表外日期拒收(不猜)');
134
- const cBad = ledger.appendCompanion({
135
- label: '爸爸',
136
- constraints: {
137
- health: [
138
- '手机号13800001111'
139
- ]
140
- },
141
- evidence: '探针'
142
- });
102
+ const cBad = ledger.appendCompanion({ label: '爸爸', constraints: { health: ['手机号13800001111'] }, evidence: '探针' });
143
103
  assert(cBad.appended === false && /负面清单/.test(cBad.reason ?? ''), '同行人负面清单拒收(证件/手机号不入库)');
144
- const cOk = ledger.appendCompanion({
145
- label: '爸爸',
146
- constraints: {
147
- health: [
148
- '轻度高血压'
149
- ]
150
- },
151
- evidence: '用户原话:爸爸65轻度高血压'
152
- });
104
+ const cOk = ledger.appendCompanion({ label: '爸爸', constraints: { health: ['轻度高血压'] }, evidence: '用户原话:爸爸65轻度高血压' });
153
105
  assert(cOk.appended === true && ledger.readCompanions().length === 1, '同行人正常入账');
154
106
  const co = ledger.confirmOutcome({
155
- wishId: w1.wish_id,
156
- attribution: 'helpful',
157
- detail: '成了',
158
- trip: {
159
- destination: '大理',
160
- start: '2026-10-01',
161
- source: 'wish-confirmed',
162
- evidence: `wish ${w1.wish_id} confirm-outcome(helpful)`
163
- }
107
+ wishId: w1.wish_id, attribution: 'helpful', detail: '成了',
108
+ trip: { destination: '大理', start: '2026-10-01', source: 'wish-confirmed', evidence: `wish ${w1.wish_id} confirm-outcome(helpful)` },
164
109
  });
165
110
  assert(co.recorded === true, 'confirm-outcome:verified_outcome 效用事件落账');
166
111
  assert(co.trip?.appended === false && /重叠/.test(co.trip?.reason ?? ''), 'confirm-outcome 单事务:行程过守门(重叠拒收,与文件版语义一致)——两写同生或同拒');
@@ -169,76 +114,25 @@ const profileBefore = ledger.readMotivation();
169
114
  ledger.db.exec('DELETE FROM projection_docs; DELETE FROM projection_items');
170
115
  ledger.rebuildProjections();
171
116
  assert(JSON.stringify(ledger.readWishPool()) === poolBefore, 'fold 重建:愿望池与直读逐字节一致');
172
- assert(JSON.stringify(ledger.readMotivation()?.weights) === JSON.stringify(profileBefore?.weights) && (ledger.readMotivation()?.evidence?.length ?? 0) === (profileBefore?.evidence?.length ?? 0), 'fold 重建:画像与直读一致');
117
+ assert(JSON.stringify(ledger.readMotivation()?.weights) === JSON.stringify(profileBefore?.weights)
118
+ && (ledger.readMotivation()?.evidence?.length ?? 0) === (profileBefore?.evidence?.length ?? 0), 'fold 重建:画像与直读一致');
173
119
  assert(ledger.readCompanions().length === 1 && ledger.readTrips().length === 1, 'fold 重建:同行人投影与行程日志一致');
174
- const wishAddedSeqs = ledger.readEvents('wish.added', 10).map((e)=>e.seq);
120
+ const wishAddedSeqs = ledger.readEvents('wish.added', 10).map(e => e.seq);
175
121
  const firstWishSeq = Math.min(...wishAddedSeqs);
176
- ledger.appendWish({
177
- name: '普吉',
178
- reason: '',
179
- conditions: {
180
- days: 5
181
- }
182
- });
122
+ ledger.appendWish({ name: '普吉', reason: '', conditions: { days: 5 } });
183
123
  ledger.rebuildProjections(firstWishSeq);
184
124
  assert(ledger.readWishPool().length === 1, `rewind 至 seq ${firstWishSeq}:投影回到历史时点(LangGraph fork 同构)`);
185
125
  ledger.rebuildProjections();
186
126
  assert(ledger.readWishPool().length === 2, 'rebuild 无参回到最新(events 是唯一权威,投影随时可重建)');
127
+ // ---- 8:one-shot 迁移 --------------------------------------------------------------
187
128
  const mroot = mkdtempSync(join(tmpdir(), 'gotry-migrate-'));
188
129
  const mdir = join(mroot, 'gotry-state');
189
- mkdirSync(mdir, {
190
- recursive: true
191
- });
192
- writeFileSync(join(mdir, 'motivation-profile.json'), JSON.stringify({
193
- weights: {
194
- curiosity: 0.6
195
- },
196
- evidence: [
197
- '原话A'
198
- ],
199
- hard: {},
200
- updated_at: '2026-08-01T00:00:00Z'
201
- }));
202
- writeFileSync(join(mdir, 'wish-pool.json'), JSON.stringify([
203
- {
204
- wish_id: 'wLEGACY1',
205
- name: '京都',
206
- conditions: {
207
- days: 7
208
- },
209
- added_at: '2026-08-01T00:00:00Z'
210
- }
211
- ]));
212
- writeFileSync(join(mdir, 'memory-utility.jsonl'), JSON.stringify({
213
- schema: 'memory_utility_observation.v0',
214
- event_id: 'wLEGACY1|recalled||',
215
- wish_id: 'wLEGACY1',
216
- kind: 'recalled',
217
- ts: '2026-08-02T00:00:00Z'
218
- }) + '\n');
219
- writeFileSync(join(mdir, 'trips.jsonl'), JSON.stringify({
220
- schema: 'travel_timeline.v1',
221
- trip_id: '京都|2026-04-01|user-verbatim',
222
- destination: '京都',
223
- start: '2026-04-01',
224
- source: 'user-verbatim',
225
- evidence: '原话',
226
- ts: '2026-08-01T00:00:00Z'
227
- }) + '\n');
228
- writeFileSync(join(mdir, 'companions.json'), JSON.stringify([
229
- {
230
- schema: 'companion_profile.v1',
231
- companion_id: '妈妈',
232
- label: '妈妈',
233
- constraints: {
234
- mobility: '步行≤3h'
235
- },
236
- evidence: [
237
- '原话'
238
- ],
239
- ts: '2026-08-01T00:00:00Z'
240
- }
241
- ]));
130
+ mkdirSync(mdir, { recursive: true });
131
+ writeFileSync(join(mdir, 'motivation-profile.json'), JSON.stringify({ weights: { curiosity: 0.6 }, evidence: ['原话A'], hard: {}, updated_at: '2026-08-01T00:00:00Z' }));
132
+ writeFileSync(join(mdir, 'wish-pool.json'), JSON.stringify([{ wish_id: 'wLEGACY1', name: '京都', conditions: { days: 7 }, added_at: '2026-08-01T00:00:00Z' }]));
133
+ writeFileSync(join(mdir, 'memory-utility.jsonl'), JSON.stringify({ schema: 'memory_utility_observation.v0', event_id: 'wLEGACY1|recalled||', wish_id: 'wLEGACY1', kind: 'recalled', ts: '2026-08-02T00:00:00Z' }) + '\n');
134
+ writeFileSync(join(mdir, 'trips.jsonl'), JSON.stringify({ schema: 'travel_timeline.v1', trip_id: '京都|2026-04-01|user-verbatim', destination: '京都', start: '2026-04-01', source: 'user-verbatim', evidence: '原话', ts: '2026-08-01T00:00:00Z' }) + '\n');
135
+ writeFileSync(join(mdir, 'companions.json'), JSON.stringify([{ schema: 'companion_profile.v1', companion_id: '妈妈', label: '妈妈', constraints: { mobility: '步行≤3h' }, evidence: ['原话'], ts: '2026-08-01T00:00:00Z' }]));
242
136
  const m1 = ensureLedger(mroot);
243
137
  assert(m1.readMotivation()?.weights?.['curiosity'] === 0.6, '迁移:画像整档入账');
244
138
  assert(String(m1.readWishPool()[0]?.wish_id) === 'wLEGACY1', '迁移:愿望保留原主键');
@@ -247,77 +141,205 @@ assert(existsSync(join(mdir, 'pre-ledger-backup', 'wish-pool.json')), '迁移:
247
141
  const migratedCount = m1.countEvents();
248
142
  ensureLedger(mroot);
249
143
  assert(m1.countEvents() === migratedCount, '重复 ensure 不重复导入(kv 旗标 + 幂等键双保险)');
144
+ const tenantMroot = mkdtempSync(join(tmpdir(), 'gotry-migrate-tenant-boundary-'));
145
+ const tenantMdir = join(tenantMroot, 'gotry-state');
146
+ mkdirSync(tenantMdir, { recursive: true });
147
+ writeFileSync(join(tenantMdir, 'wish-pool.json'), JSON.stringify([{ wish_id: 'wLOCALLEGACY', name: '本地旧愿望', conditions: { days: 2 }, added_at: '2026-08-01T00:00:00Z' }]));
148
+ assert(!ledgerExists(tenantMroot), '迁移边界:非 local fallback 前 DB 不存在');
149
+ assert(readWishPoolWithFallback(tenantMroot, 'tenant-a').length === 0 && !ledgerExists(tenantMroot), '迁移边界:非 local 只读 fallback 不读取旧本地 JSON,也不创建 DB');
150
+ assert(existsSync(join(tenantMdir, 'wish-pool.json')), '迁移边界:非 local 只读 fallback 不触碰 legacy 文件');
151
+ const tenantFirst = ensureLedger(tenantMroot, 'tenant-a');
152
+ const localLegacy = ensureLedger(tenantMroot);
153
+ assert(tenantFirst.readWishPool().length === 0, '迁移边界:非 local 首次打开不把旧文件猜入该租户');
154
+ assert(localLegacy.readWishPool()[0]?.wish_id === 'wLOCALLEGACY', '迁移边界:旧 JSON/JSONL 只可安全归入默认 local');
155
+ assert(localLegacy.db.prepare('SELECT COUNT(*) AS n FROM events WHERE tenant_id != \'local\'').get().n === 0, '迁移边界:旧文件导入事件 tenant_id 全为 local,不伪造历史归属');
156
+ const v1root = mkdtempSync(join(tmpdir(), 'gotry-v1-tenant-migration-'));
157
+ mkdirSync(join(v1root, 'gotry-state'), { recursive: true });
158
+ const v1db = new Database(ledgerDbPath(v1root));
159
+ v1db.exec(`
160
+ CREATE TABLE events (
161
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
162
+ ts TEXT NOT NULL,
163
+ actor TEXT NOT NULL,
164
+ kind TEXT NOT NULL,
165
+ subject_id TEXT NOT NULL DEFAULT '',
166
+ payload TEXT NOT NULL,
167
+ idem_key TEXT,
168
+ run_id TEXT
169
+ );
170
+ CREATE UNIQUE INDEX events_idem ON events(idem_key) WHERE idem_key IS NOT NULL;
171
+ `);
172
+ v1db.prepare('INSERT INTO events (ts, actor, kind, subject_id, payload, idem_key, run_id) VALUES (?, ?, ?, ?, ?, ?, NULL)')
173
+ .run('2026-08-28T00:00:00.000Z', 'system:v1', 'wish.added', 'wV1', JSON.stringify({ wish: { wish_id: 'wV1', name: 'v1旧愿望', reason: '', conditions: { days: 4 }, added_at: '2026-08-28T00:00:00.000Z' } }), 'v1:wish');
174
+ v1db.close();
175
+ const v1Tenant = ensureLedger(v1root, 'tenant-a');
176
+ const v1Local = ensureLedger(v1root);
177
+ assert(v1Tenant.readWishPool().length === 0, 'schema v1→v2:非 local 打开旧 DB 不吸收 local 事件');
178
+ assert(v1Local.readWishPool()[0]?.wish_id === 'wV1', 'schema v1→v2:旧 events 回填 local 后重建 local 投影');
179
+ assert(v1Local.db.prepare('SELECT COUNT(*) AS n FROM events WHERE tenant_id = \'local\'').get().n === 1, 'schema v1→v2:events tenant_id 回填为 local 且不丢 seq/idempotency');
180
+ const tenantMrootProc = mkdtempSync(join(tmpdir(), 'gotry-migrate-tenant-boundary-proc-'));
181
+ const tenantMdirProc = join(tenantMrootProc, 'gotry-state');
182
+ mkdirSync(tenantMdirProc, { recursive: true });
183
+ writeFileSync(join(tenantMdirProc, 'wish-pool.json'), JSON.stringify([{ wish_id: 'wPROCLEGACY', name: '子进程旧愿望', reason: 'legacy-local', conditions: { days: 3 }, added_at: '2026-08-03T00:00:00Z' }]));
184
+ const legacyProc = spawnSync('npx', ['tsx', '--eval', `
185
+ import { ensureLedger, ledgerExists, readWishPoolWithFallback } from './src/state-ledger.js'
186
+ const root = ${JSON.stringify(tenantMrootProc)}
187
+ if (ledgerExists(root)) process.exit(2)
188
+ if (readWishPoolWithFallback(root, 'tenant-a').length !== 0 || ledgerExists(root)) process.exit(3)
189
+ const tenant = ensureLedger(root, 'tenant-a')
190
+ const local = ensureLedger(root)
191
+ const foreign = local.db.prepare("SELECT COUNT(*) AS n FROM events WHERE tenant_id != 'local'").get().n
192
+ if (tenant.readWishPool().length !== 0 || local.readWishPool()[0]?.wish_id !== 'wPROCLEGACY' || foreign !== 0) process.exit(4)
193
+ `], { encoding: 'utf-8', cwd: process.cwd(), env: process.env });
194
+ assert(legacyProc.status === 0, `legacy JSON 跨进程首次非 local 迁移只归 local 且 fallback 不创建 DB(实际 ${legacyProc.status}:${(legacyProc.stderr ?? '').slice(0, 300)})`);
195
+ const v1rootProc = mkdtempSync(join(tmpdir(), 'gotry-v1-tenant-migration-proc-'));
196
+ mkdirSync(join(v1rootProc, 'gotry-state'), { recursive: true });
197
+ const v1dbProc = new Database(ledgerDbPath(v1rootProc));
198
+ v1dbProc.exec(`
199
+ CREATE TABLE events (
200
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
201
+ ts TEXT NOT NULL,
202
+ actor TEXT NOT NULL,
203
+ kind TEXT NOT NULL,
204
+ subject_id TEXT NOT NULL DEFAULT '',
205
+ payload TEXT NOT NULL,
206
+ idem_key TEXT,
207
+ run_id TEXT
208
+ );
209
+ CREATE UNIQUE INDEX events_idem ON events(idem_key) WHERE idem_key IS NOT NULL;
210
+ `);
211
+ v1dbProc.prepare('INSERT INTO events (ts, actor, kind, subject_id, payload, idem_key, run_id) VALUES (?, ?, ?, ?, ?, ?, NULL)')
212
+ .run('2026-08-28T00:00:00.000Z', 'system:v1', 'wish.added', 'wV1PROC', JSON.stringify({ wish: { wish_id: 'wV1PROC', name: 'v1子进程旧愿望', reason: 'v1-local', conditions: { days: 5 }, added_at: '2026-08-28T00:00:00.000Z' } }), 'v1proc:wish');
213
+ v1dbProc.close();
214
+ const v1Proc = spawnSync('npx', ['tsx', '--eval', `
215
+ import { ensureLedger } from './src/state-ledger.js'
216
+ const root = ${JSON.stringify(v1rootProc)}
217
+ const tenant = ensureLedger(root, 'tenant-a')
218
+ const local = ensureLedger(root)
219
+ const localRows = local.db.prepare("SELECT COUNT(*) AS n FROM events WHERE tenant_id = 'local'").get().n
220
+ if (tenant.readWishPool().length !== 0 || local.readWishPool()[0]?.wish_id !== 'wV1PROC' || localRows !== 1) process.exit(5)
221
+ `], { encoding: 'utf-8', cwd: process.cwd(), env: process.env });
222
+ assert(v1Proc.status === 0, `schema v1→v2 跨进程首次非 local 打开不吸收 local 事件(实际 ${v1Proc.status}:${(v1Proc.stderr ?? '').slice(0, 300)})`);
223
+ // ---- 9:durable 工单崩溃恢复(exactly-once) ----------------------------------------
250
224
  const wroot = mkdtempSync(join(tmpdir(), 'gotry-wf-'));
251
225
  const countFile = join(wroot, 'solve-count.txt');
252
226
  writeFileSync(countFile, '');
253
- const crash = spawnSync('npx', [
254
- 'tsx',
255
- 'scripts/ledger-workflow-crash.ts',
256
- wroot,
257
- 'dp-crash1',
258
- countFile
259
- ], {
260
- encoding: 'utf-8'
261
- });
227
+ const crash = spawnSync('npx', ['tsx', 'scripts/ledger-workflow-crash.ts', wroot, 'dp-crash1', countFile], { encoding: 'utf-8' });
262
228
  assert(crash.status === 9, `崩溃探针按设计 exit 9(实际 ${crash.status}:${(crash.stderr ?? '').slice(0, 200)})`);
263
229
  const runRow = openLedgerIfExists(wroot)?.getWorkflowRun('dp-crash1');
264
230
  assert(runRow?.status === 'pending', '崩溃后工单仍 pending(账本权威未损)');
265
231
  const count1 = readFileSync(countFile, 'utf-8').split('\n').filter(Boolean).length;
266
232
  assert(count1 === 1, '崩溃前真实求解恰 1 次');
267
- const resume = spawnSync('npx', [
268
- 'tsx',
269
- 'scripts/async-collect.ts',
270
- 'dp-crash1',
271
- wroot
272
- ], {
273
- encoding: 'utf-8',
274
- env: {
275
- ...process.env,
276
- GOTRY_SOLVE_COUNT_FILE: countFile
277
- }
233
+ const resume = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', 'dp-crash1', wroot], {
234
+ encoding: 'utf-8', env: { ...process.env, GOTRY_SOLVE_COUNT_FILE: countFile },
278
235
  });
279
- assert(resume.status === 0, `恢复进程 exit 0(实际 ${resume.status}:${(resume.stderr ?? '').slice(0, 300)})`);
236
+ const failedOutcome = terminalOutcomeOf(resume.stdout ?? '');
237
+ assert(resume.status === 2, `非4/4 恢复进程 exit 2(实际 ${resume.status}:${(resume.stderr ?? '').slice(0, 300)})`);
238
+ assert(failedOutcome?.status === 'failed'
239
+ && failedOutcome.ticket_id === 'dp-crash1'
240
+ && failedOutcome.passed < failedOutcome.total
241
+ && failedOutcome.failed_checks.length > 0, '非4/4 输出 gotry_async_terminal.v1/failed 与失败项');
280
242
  const count2 = readFileSync(countFile, 'utf-8').split('\n').filter(Boolean).length;
281
243
  assert(count2 === 1, '恢复时 done 步骤零重算(exactly-once:求解计数仍 1,不重复花钱)');
282
- assert(existsSync(join(wroot, 'gotry-state', 'async', 'dp-crash1.deliverable.md')), '交付物视图已落盘(.deliverable.md)');
283
- assert(openLedgerIfExists(wroot)?.getWorkflowRun('dp-crash1')?.status === 'settled', '账本终态 settled');
284
- const replay = spawnSync('npx', [
285
- 'tsx',
286
- 'scripts/async-collect.ts',
287
- 'dp-crash1',
288
- wroot
289
- ], {
290
- encoding: 'utf-8',
291
- env: {
292
- ...process.env,
293
- GOTRY_SOLVE_COUNT_FILE: countFile
294
- }
244
+ const failedDeliverablePath = join(wroot, 'gotry-state', 'async', 'dp-crash1.deliverable.md');
245
+ assert(existsSync(failedDeliverablePath), '交付物视图已落盘(.deliverable.md)');
246
+ const failedLedger = openLedgerIfExists(wroot);
247
+ assert(failedLedger?.getWorkflowRun('dp-crash1')?.status === 'failed', '非4/4 账本终态 failed');
248
+ assert(JSON.stringify(failedLedger?.getWorkflowTerminalOutcome('dp-crash1')) === JSON.stringify(failedOutcome), '非4/4 结构化终态随 async.failed 事件落账');
249
+ const replay = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', 'dp-crash1', wroot], {
250
+ encoding: 'utf-8', env: { ...process.env, GOTRY_SOLVE_COUNT_FILE: countFile },
295
251
  });
296
- assert(replay.status === 0, '终态复诵 exit 0(幂等)');
252
+ assert(replay.status === 2 && JSON.stringify(terminalOutcomeOf(replay.stdout ?? '')) === JSON.stringify(failedOutcome), 'failed 终态复诵保持 exit 2 与同一结构化结果');
297
253
  const count3 = readFileSync(countFile, 'utf-8').split('\n').filter(Boolean).length;
298
254
  assert(count3 === 1, '终态复诵零重算');
299
- const pw1 = ledger.requestPendingWrite({
300
- idemKey: 'booking:demo-1',
301
- seam: 'flight-order-confirm',
302
- payload: {
303
- flight: 'MU123'
304
- }
255
+ const failedDeliverable = readFileSync(failedDeliverablePath, 'utf-8');
256
+ rmSync(failedDeliverablePath);
257
+ const replayAfterMissingView = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', 'dp-crash1', wroot], {
258
+ encoding: 'utf-8', env: { ...process.env, GOTRY_SOLVE_COUNT_FILE: countFile },
305
259
  });
306
- const pw2 = ledger.requestPendingWrite({
307
- idemKey: 'booking:demo-1',
308
- seam: 'flight-order-confirm',
309
- payload: {
310
- flight: 'MU123'
311
- }
260
+ assert(replayAfterMissingView.status === 2
261
+ && JSON.stringify(terminalOutcomeOf(replayAfterMissingView.stdout ?? '')) === JSON.stringify(failedOutcome), 'failed 终态缺失视图时仍复诵同一 exit 2 与结构化结果');
262
+ assert(existsSync(failedDeliverablePath) && readFileSync(failedDeliverablePath, 'utf-8') === failedDeliverable, 'failed 终态复诵重建缺失 .deliverable.md 视图');
263
+ const countAfterViewRepair = readFileSync(countFile, 'utf-8').split('\n').filter(Boolean).length;
264
+ assert(countAfterViewRepair === 1, '终态视图修复零重算');
265
+ // state-cli tick 是另一个 durable driver;即使旧调用未显式透传 outcome,
266
+ // settle 层也必须从账本中的权威 state/step 恢复机器终态,不能误结算为成功。
267
+ const tickRoot = mkdtempSync(join(tmpdir(), 'gotry-wf-tick-failed-'));
268
+ const tickTicket = {
269
+ id: 'dp-tick-failed1',
270
+ objective: 'state-cli 非4/4 机器终态回归',
271
+ requestedAt: new Date().toISOString(),
272
+ etaLabel: '秒级',
273
+ };
274
+ const tickState = {
275
+ calendar: { year: 2026, assertedWeekdays: {} },
276
+ profile: {},
277
+ gates: [],
278
+ wishes: [],
279
+ };
280
+ await persistAsyncTicket(tickTicket, tickState, tickRoot);
281
+ const tick = spawnSync('npx', ['tsx', 'scripts/state-cli.ts', 'tick', tickRoot], { encoding: 'utf-8' });
282
+ assert(tick.status === 0, `state-cli tick 完成回收(实际 ${tick.status}:${(tick.stderr ?? '').slice(0, 200)})`);
283
+ const tickLedger = openLedgerIfExists(tickRoot);
284
+ const tickOutcome = tickLedger?.getWorkflowTerminalOutcome(tickTicket.id);
285
+ assert(tickLedger?.getWorkflowRun(tickTicket.id)?.status === 'failed'
286
+ && tickOutcome?.schema === 'gotry_async_terminal.v1'
287
+ && tickOutcome.status === 'failed'
288
+ && tickOutcome.passed === 0
289
+ && tickOutcome.failed_checks.length === 4, 'state-cli tick 未透传 outcome 时仍从权威账本恢复非4/4 failed 终态');
290
+ const tickReplay = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', tickTicket.id, tickRoot], { encoding: 'utf-8' });
291
+ assert(tickReplay.status === 2
292
+ && JSON.stringify(terminalOutcomeOf(tickReplay.stdout ?? '')) === JSON.stringify(tickOutcome), 'state-cli 写入的 failed 终态可由 collector 幂等复诵为同一 outcome/exit 2');
293
+ const successRoot = mkdtempSync(join(tmpdir(), 'gotry-wf-success-'));
294
+ const successCountFile = join(successRoot, 'solve-count.txt');
295
+ writeFileSync(successCountFile, '');
296
+ const successTicket = {
297
+ id: 'dp-success1',
298
+ objective: '4/4 机器终态回归',
299
+ requestedAt: new Date().toISOString(),
300
+ etaLabel: '秒级',
301
+ };
302
+ const successSpec = parseFlightPackToSpec(JSON.parse(readFileSync(join('..', 'data', 'flights_2026.json'), 'utf-8')));
303
+ successSpec.budgetCny = 9000;
304
+ const successState = {
305
+ calendar: { year: 2026, assertedWeekdays: {} },
306
+ profile: {},
307
+ gates: [],
308
+ wishes: [],
309
+ spec: successSpec,
310
+ };
311
+ await persistAsyncTicket(successTicket, successState, successRoot);
312
+ const success = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', successTicket.id, successRoot], {
313
+ encoding: 'utf-8', env: { ...process.env, GOTRY_SOLVE_COUNT_FILE: successCountFile },
312
314
  });
315
+ const successOutcome = terminalOutcomeOf(success.stdout ?? '');
316
+ assert(success.status === 0
317
+ && successOutcome?.status === 'succeeded'
318
+ && successOutcome.ticket_id === successTicket.id
319
+ && successOutcome.passed === 4
320
+ && successOutcome.total === 4
321
+ && successOutcome.failed_checks.length === 0, `4/4 输出 gotry_async_terminal.v1/succeeded 且 exit 0(实际 ${success.status})`);
322
+ const successLedger = openLedgerIfExists(successRoot);
323
+ assert(successLedger?.getWorkflowRun(successTicket.id)?.status === 'settled'
324
+ && successLedger.getWorkflowTerminalOutcome(successTicket.id)?.['status'] === 'succeeded', '4/4 账本终态 settled 且结构化结果随 async.settled 落账');
325
+ const successReplay = spawnSync('npx', ['tsx', 'scripts/async-collect.ts', successTicket.id, successRoot], {
326
+ encoding: 'utf-8', env: { ...process.env, GOTRY_SOLVE_COUNT_FILE: successCountFile },
327
+ });
328
+ const successCount = readFileSync(successCountFile, 'utf-8').split('\n').filter(Boolean).length;
329
+ assert(successReplay.status === 0
330
+ && successCount === 1
331
+ && JSON.stringify(terminalOutcomeOf(successReplay.stdout ?? '')) === JSON.stringify(successOutcome), 'succeeded 终态复诵保持 exit 0、同一结构化结果且零重算');
332
+ // ---- 10:pending_writes saga + what-if 分叉 -----------------------------------------
333
+ const pw1 = ledger.requestPendingWrite({ idemKey: 'booking:demo-1', seam: 'flight-order-confirm', payload: { flight: 'MU123' } });
334
+ const pw2 = ledger.requestPendingWrite({ idemKey: 'booking:demo-1', seam: 'flight-order-confirm', payload: { flight: 'MU123' } });
313
335
  assert(pw1.created === true && pw2.created === false, 'pending_writes 幂等键去重(同一确认不可能登记两次)');
314
336
  const cf1 = ledger.confirmPendingWrite('booking:demo-1', 'PNR-ABC');
315
337
  const cf2 = ledger.confirmPendingWrite('booking:demo-1', 'PNR-ABC2');
316
338
  assert(cf1.ok === true && cf2.ok === false && cf2.status === 'confirmed', 'L3 确认只能发生一次,二连击被拒');
317
339
  const cp1 = ledger.compensatePendingWrite('booking:demo-1', '用户改签退款');
318
340
  assert(cp1.ok === true && ledger.listPendingWrites()[0]?.status === 'compensated', 'saga 补偿可达(pending/confirmed → compensated)');
319
- const audit = ledger.readEvents(undefined, 100).filter((e)=>e.kind.startsWith('write.'));
320
- assert(audit.some((e)=>e.kind === 'write.pending') && audit.some((e)=>e.kind === 'write.confirmed') && audit.some((e)=>e.kind === 'write.compensated'), '写权审计事件链完整(append-only,WriteGate 的 receipt 落点)');
341
+ const audit = ledger.readEvents(undefined, 100).filter(e => e.kind.startsWith('write.'));
342
+ assert(audit.some(e => e.kind === 'write.pending') && audit.some(e => e.kind === 'write.confirmed') && audit.some(e => e.kind === 'write.compensated'), '写权审计事件链完整(append-only,WriteGate 的 receipt 落点)');
321
343
  const forkPath = join(root, 'whatif.db');
322
344
  ledger.forkWhatIf(forkPath);
323
345
  const forkDb = new Database(forkPath);
@@ -325,20 +347,134 @@ forkDb.prepare("INSERT INTO events (ts, actor, kind, subject_id, payload) VALUES
325
347
  const forkCount = forkDb.prepare('SELECT COUNT(*) AS n FROM events').get().n;
326
348
  forkDb.close();
327
349
  assert(ledger.countEvents() === forkCount - 1, 'what-if 分叉:副本写入不触正本(VACUUM INTO 预演)');
328
- rmSync(root, {
329
- recursive: true,
330
- force: true
331
- });
332
- rmSync(mroot, {
333
- recursive: true,
334
- force: true
335
- });
336
- rmSync(wroot, {
337
- recursive: true,
338
- force: true
339
- });
350
+ // ---- 11:双形态(ADR-16):tenant scope 贯穿 append/read/fold/rebuild/API ----------------
351
+ const tenantRoot = mkdtempSync(join(tmpdir(), 'gotry-tenant-scope-'));
352
+ const localLedger = ensureLedger(tenantRoot); // 默认 tenant='local'
353
+ const tenantA = ensureLedger(tenantRoot, 'tenant-a');
354
+ const tenantB = ensureLedger(tenantRoot, 'tenant-b');
355
+ const localWish = localLedger.appendWish({ name: '同 id 愿望', reason: 'local0', conditions: { owner: 'local0' } });
356
+ const wishA = tenantA.appendWish({ name: '同 id 愿望', reason: 'a0', conditions: { owner: 'a0' } });
357
+ const wishB = tenantB.appendWish({ name: '同 id 愿望', reason: 'b0', conditions: { owner: 'b0' } });
358
+ tenantA.appendWish({ name: '同 id 愿望', reason: 'a1', conditions: { owner: 'a1' } });
359
+ tenantB.appendWish({ name: '同 id 愿望', reason: 'b1', conditions: { owner: 'b1' } });
360
+ const utilA = tenantA.appendUtilityEvent({ wish_id: wishA.wish_id, kind: 'recalled', ts: '2026-09-08T00:00:00.000Z', ctx: 'same-ctx' });
361
+ const utilB = tenantB.appendUtilityEvent({ wish_id: wishB.wish_id, kind: 'recalled', ts: '2026-09-08T00:00:00.000Z', ctx: 'same-ctx' });
362
+ assert(localWish.wish_id === wishA.wish_id && wishA.wish_id === wishB.wish_id, '同业务 id/wish_id 可跨 local/tenant-a/tenant-b 并存');
363
+ assert(utilA.appended === true && utilB.appended === true, '同 event_id/idempotency key 跨租户独立追加(UNIQUE tenant-scoped)');
364
+ const tenantRows = localLedger.db.prepare('SELECT tenant_id, kind, subject_id, idem_key FROM events WHERE subject_id = ? ORDER BY tenant_id, seq').all(wishA.wish_id);
365
+ assert(tenantRows.filter(r => r.kind === 'wish.added').map(r => r.tenant_id).sort().join(',') === 'local,tenant-a,tenant-b'
366
+ && tenantRows.filter(r => r.kind === 'wish.updated').map(r => r.tenant_id).sort().join(',') === 'tenant-a,tenant-b', '非 local 写入 events 保留真实 owner,默认 local 兼容不变');
367
+ assert(tenantA.readEvents(undefined, 100).every(e => e.tenant_id === 'tenant-a')
368
+ && tenantB.readEvents(undefined, 100).every(e => e.tenant_id === 'tenant-b')
369
+ && localLedger.readEvents(undefined, 100).every(e => e.tenant_id === 'local'), 'readEvents 默认与 kind 过滤前均先按 ledger.tenant 隔离');
370
+ assert(tenantA.readEvents('wish.added', 10).length === 1 && tenantB.readEvents('wish.added', 10).length === 1, 'readEvents(kind) 也按租户过滤');
371
+ assert(!localLedger.readWishPool().some(w => w.conditions.owner === 'a1')
372
+ && !tenantA.readWishPool().some(w => w.conditions.owner === 'b1'), '直写投影:默认 local/tenant-a/tenant-b 同 id 互不覆盖');
373
+ const localBeforeRebuild = JSON.stringify(localLedger.readWishPool());
374
+ const aBeforeRebuild = JSON.stringify(tenantA.readWishPool());
375
+ const bBeforeRebuild = JSON.stringify(tenantB.readWishPool());
376
+ tenantA.rebuildProjections();
377
+ assert(JSON.stringify(tenantA.readWishPool()) === aBeforeRebuild, 'tenant-a rebuild 只读自己的 events 并还原交错 update 后投影');
378
+ assert(JSON.stringify(tenantB.readWishPool()) === bBeforeRebuild && JSON.stringify(localLedger.readWishPool()) === localBeforeRebuild, 'tenant-a rebuild 不改写 tenant-b/local 投影');
379
+ tenantA.rebuildProjections();
380
+ assert(JSON.stringify(tenantA.readWishPool()) === aBeforeRebuild, 'tenant-a 重复 rebuild 幂等');
381
+ tenantB.rebuildProjections();
382
+ assert(JSON.stringify(tenantB.readWishPool()) === bBeforeRebuild, 'tenant-b rebuild 在 tenant-a 同 item_id 存在时不串读 wish.updated fold 行');
383
+ localLedger.rebuildProjections();
384
+ assert(JSON.stringify(localLedger.readWishPool()) === localBeforeRebuild, '默认 local rebuild 兼容且不吸收非 local events');
385
+ const reopenScript = `
386
+ import { ensureLedger } from './src/state-ledger.js'
387
+ const root = ${JSON.stringify(tenantRoot)}
388
+ const a = ensureLedger(root, 'tenant-a')
389
+ const b = ensureLedger(root, 'tenant-b')
390
+ const local = ensureLedger(root)
391
+ const aWish = a.readWishPool()[0]
392
+ const bWish = b.readWishPool()[0]
393
+ const localWish = local.readWishPool()[0]
394
+ const ok = aWish?.name === '同 id 愿望'
395
+ && bWish?.name === '同 id 愿望'
396
+ && localWish?.name === '同 id 愿望'
397
+ && aWish?.conditions?.owner === 'a1'
398
+ && bWish?.conditions?.owner === 'b1'
399
+ && localWish?.conditions?.owner === 'local0'
400
+ && a.readEvents(undefined, 100).every(e => e.tenant_id === 'tenant-a')
401
+ && b.readEvents(undefined, 100).every(e => e.tenant_id === 'tenant-b')
402
+ && local.readEvents(undefined, 100).every(e => e.tenant_id === 'local')
403
+ if (!ok) {
404
+ console.error(JSON.stringify({ aWish, bWish, localWish, aEvents: a.readEvents(undefined, 100), bEvents: b.readEvents(undefined, 100), localEvents: local.readEvents(undefined, 100) }))
405
+ process.exit(1)
406
+ }
407
+ `;
408
+ const reopenTenant = spawnSync('npx', ['tsx', '--eval', reopenScript], { encoding: 'utf-8', cwd: process.cwd(), env: process.env });
409
+ assert(reopenTenant.status === 0, `跨进程 reopen 后租户事件/投影仍隔离(实际 ${reopenTenant.status}:${(reopenTenant.stderr ?? '').slice(0, 300)})`);
410
+ assert(localLedger.tenant === 'local' && tenantA.tenant === 'tenant-a' && tenantB.tenant === 'tenant-b', 'tenant_id 从第一天就是一等字段');
411
+ const probeASeq = tenantA.insertEvent({ actor: 'test:tenant-scope', kind: 'tenant.probe', subjectId: 'probe-a', payload: { tenant: 'A' }, ts: '2026-09-08T00:00:10.000Z' });
412
+ const probeBSeq = tenantB.insertEvent({ actor: 'test:tenant-scope', kind: 'tenant.probe', subjectId: 'probe-b-newer', payload: { tenant: 'B' }, ts: '2026-09-08T00:00:11.000Z' });
413
+ const aProbe = tenantA.readEvents('tenant.probe', 1)[0];
414
+ assert((probeASeq ?? 0) < (probeBSeq ?? 0) && aProbe?.tenant_id === 'tenant-a' && aProbe.subject_id === 'probe-a', 'readEvents(kind, limit) 先按 tenant 过滤再截断:tenant-b 较新事件不抢 tenant-a limit=1');
415
+ const foldFixtureRoot = mkdtempSync(join(tmpdir(), 'gotry-tenant-fold-fixture-'));
416
+ const foldA = ensureLedger(foldFixtureRoot, 'tenant-a');
417
+ const foldB = ensureLedger(foldFixtureRoot, 'tenant-b');
418
+ const sameFoldId = 'w-fold-same-id';
419
+ const foldWishA = { wish_id: sameFoldId, name: '同主键反例', reason: 'A-fixed-reason', conditions: { owner: 'A-fixed', days: 5 }, added_at: '2026-09-08T00:00:00.000Z' };
420
+ const foldWishB = { wish_id: sameFoldId, name: '同主键反例', reason: 'B-fixed-reason', conditions: { owner: 'B-fixed', days: 9 }, added_at: '2026-09-08T00:00:01.000Z' };
421
+ const foldInsert = foldA.db.prepare(`INSERT INTO events (tenant_id, ts, actor, kind, subject_id, payload, idem_key, run_id) VALUES (?, ?, 'test:fold-fixture', ?, ?, ?, ?, NULL)`);
422
+ foldInsert.run('tenant-a', '2026-09-08T00:00:00.000Z', 'wish.added', sameFoldId, JSON.stringify({ wish: foldWishA }), 'fold:a:add');
423
+ foldInsert.run('tenant-b', '2026-09-08T00:00:01.000Z', 'wish.added', sameFoldId, JSON.stringify({ wish: foldWishB }), 'fold:b:add');
424
+ foldInsert.run('tenant-b', '2026-09-08T00:00:02.000Z', 'wish.updated', sameFoldId, JSON.stringify({ wish_id: sameFoldId, patch: { muted: true } }), 'fold:b:muted');
425
+ const fixtureEvents = foldA.db.prepare('SELECT tenant_id, kind, subject_id FROM events WHERE subject_id = ? ORDER BY seq').all(sameFoldId);
426
+ assert(fixtureEvents.map(e => `${e.tenant_id}:${e.kind}`).join(',') === 'tenant-a:wish.added,tenant-b:wish.added,tenant-b:wish.updated', '确定性 fold fixture:同 wish_id 的 A/B events 拥有固定且正确 tenant 归属');
427
+ foldA.rebuildProjections();
428
+ const foldABefore = JSON.stringify(foldA.readWishPool()[0]);
429
+ foldB.rebuildProjections();
430
+ const foldAAfter = foldA.readWishPool()[0];
431
+ const foldBAfter = foldB.readWishPool()[0];
432
+ const unscopedFoldRow = foldB.db.prepare(`SELECT doc FROM projection_items WHERE subject = 'wish_pool' AND item_id = ?`).get(sameFoldId);
433
+ const unscopedFoldDoc = unscopedFoldRow ? JSON.parse(unscopedFoldRow.doc) : null;
434
+ assert(JSON.stringify(foldAAfter) === foldABefore, '精确 fold 反例:A rebuild 后字段固定,B rebuild 不改变 A 投影');
435
+ assert(foldBAfter?.reason === 'B-fixed-reason'
436
+ && foldBAfter.conditions.owner === 'B-fixed'
437
+ && foldBAfter.conditions.days === 9
438
+ && foldBAfter.added_at === '2026-09-08T00:00:01.000Z'
439
+ && foldBAfter.muted === true, '精确 fold 反例:B 仅 patch muted=true 后仍保留 B 的 reason/conditions/added_at');
440
+ assert(unscopedFoldDoc?.reason === 'A-fixed-reason', '负向控制:若 wish.updated fold 去掉 tenant 条件,该 fixture 会稳定先读 A 行并把 B 变成 A');
441
+ const workflowRoot = mkdtempSync(join(tmpdir(), 'gotry-tenant-workflow-'));
442
+ const wfA = ensureLedger(workflowRoot, 'tenant-a');
443
+ const wfB = ensureLedger(workflowRoot, 'tenant-b');
444
+ wfA.createWorkflowRun({ id: 'wf-same', goal: 'tenant-a-goal', ticket: { tenant: 'A' }, state: { tenant: 'A' } });
445
+ wfB.createWorkflowRun({ id: 'wf-same', goal: 'tenant-b-goal', ticket: { tenant: 'B' }, state: { tenant: 'B' } });
446
+ wfB.markStepIntent('wf-same', 'step-1');
447
+ wfB.markStepDone('wf-same', 'step-1', { ok: true });
448
+ wfB.settleWorkflowRun('wf-same', 'tenant-b deliverable', { schema: 'test', status: 'succeeded' });
449
+ const workflowRows = wfA.db.prepare('SELECT tenant_id, goal, status, deliverable FROM workflow_runs WHERE id = ? ORDER BY tenant_id').all('wf-same');
450
+ assert(wfA.getWorkflowRun('wf-same')?.status === 'pending'
451
+ && wfB.getWorkflowRun('wf-same')?.status === 'settled'
452
+ && workflowRows.map(r => `${r.tenant_id}:${r.goal}:${r.status}:${r.deliverable ?? ''}`).join('|') === 'tenant-a:tenant-a-goal:pending:|tenant-b:tenant-b-goal:settled:tenant-b deliverable', '非 local workflow_runs 同 id 行按 tenant 绑定,tenant-b settle 不改 tenant-a pending');
453
+ assert(wfA.getWorkflowStep('wf-same', 'step-1') === undefined && wfB.getWorkflowStep('wf-same', 'step-1')?.status === 'done', '非 local workflow_steps 行按 tenant 绑定');
454
+ const wfBAsyncEvents = wfB.readEvents(undefined, 20).filter(e => e.kind.startsWith('async.'));
455
+ assert(wfBAsyncEvents.some(e => e.kind === 'async.settled') && wfBAsyncEvents.every(e => e.tenant_id === 'tenant-b'), '非 local workflow 审计事件 owner 不回落 local且含 settled 终态');
456
+ const forgetRoot = mkdtempSync(join(tmpdir(), 'gotry-tenant-forget-'));
457
+ const forgetA = ensureLedger(forgetRoot, 'tenant-a');
458
+ const forgetB = ensureLedger(forgetRoot, 'tenant-b');
459
+ const forgetAId = forgetA.appendWish({ name: '同 id 可忘愿望', reason: 'keep-A', conditions: { owner: 'A' } }).wish_id;
460
+ const forgetBId = forgetB.appendWish({ name: '同 id 可忘愿望', reason: 'drop-B', conditions: { owner: 'B' } }).wish_id;
461
+ forgetB.forgetSubject([{ kinds: ['wish.added', 'wish.updated', 'memory_utility.event'], subjectId: forgetBId }]);
462
+ const forgetRows = forgetA.db.prepare('SELECT tenant_id, COUNT(*) AS n FROM events WHERE subject_id = ? GROUP BY tenant_id ORDER BY tenant_id').all(forgetAId);
463
+ assert(forgetAId === forgetBId && forgetA.readWishPool().length === 1 && forgetB.readWishPool().length === 0, '非 local forget 只删目标 tenant 投影,A 同 id 愿望不受影响');
464
+ assert(forgetRows.map(r => `${r.tenant_id}:${r.n}`).join(',') === 'tenant-a:1' && forgetB.readEvents('forget.executed', 1)[0]?.tenant_id === 'tenant-b', '非 local forget 事件删除范围与审计事件 owner 均按 tenant 绑定');
465
+ // ---- 收尾 --------------------------------------------------------------------------
466
+ rmSync(root, { recursive: true, force: true });
467
+ rmSync(mroot, { recursive: true, force: true });
468
+ rmSync(tenantMroot, { recursive: true, force: true });
469
+ rmSync(tenantMrootProc, { recursive: true, force: true });
470
+ rmSync(v1root, { recursive: true, force: true });
471
+ rmSync(v1rootProc, { recursive: true, force: true });
472
+ rmSync(tenantRoot, { recursive: true, force: true });
473
+ rmSync(foldFixtureRoot, { recursive: true, force: true });
474
+ rmSync(workflowRoot, { recursive: true, force: true });
475
+ rmSync(forgetRoot, { recursive: true, force: true });
476
+ rmSync(wroot, { recursive: true, force: true });
477
+ rmSync(successRoot, { recursive: true, force: true });
340
478
  console.log(`\nLEDGER TESTS: ${pass} ok, ${fail} fail`);
341
- if (fail > 0) process.exit(1);
342
-
343
-
344
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/ledger-tests.ts
479
+ if (fail > 0)
480
+ process.exit(1);