@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
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 春节锚点表生成器(issue #274,D-9 清偿:手抄常量表已被否决——「换个日子的同款债务」)。
3
+ *
4
+ * 用 lunar-typescript(devDependency,MIT,零传递依赖,不进 runtime bundle)在构建期把
5
+ * 正月初一锚点机械生成到 SPRING_FESTIVAL 表,回写 ../src/time-anchor.ts 的生成块
6
+ * ([gen:lunar-anchors:begin/end] 标记之间);首次运行会把旧手抄表原位迁移为生成块。
7
+ * 扩表/换库 = 重跑本命令;表未变则为无 diff 幂等(生成时间只在表真变时刷新)。
8
+ *
9
+ * 运行(在 ts/ 下):
10
+ * npx tsx scripts/gen-lunar-anchors.ts # 生成并回写(全离线,不访问网络)
11
+ * npx tsx scripts/gen-lunar-anchors.ts --check # 只比对不写:表/库版本漂移即非零退出
12
+ * # (挂 run-all-tests.sh §58 漂移闸)
13
+ *
14
+ * 交叉验证不在这里做:2026-2031 旧表 6 条 + 港天文台 2032-2040 核实 9 条已固化为
15
+ * time-eval §7 的双 oracle 断言(红→绿:生成前 oracle B 必红)。
16
+ */
17
+ import { readFileSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { Lunar } from 'lunar-typescript';
20
+ const TARGET = join(import.meta.dirname, '..', 'src', 'time-anchor.ts');
21
+ const BEGIN = '// [gen:lunar-anchors:begin] 生成块起点——手改会被下次生成覆盖';
22
+ const END = '// [gen:lunar-anchors:end]';
23
+ const START_YEAR = 2026;
24
+ const END_YEAR = 2099;
25
+ /** 正月初一 → 公历 YYYY-MM-DD(lunar-typescript:农历年 y 正月初一 的 Solar)。 */
26
+ function springFestivalOf(lunarYear) {
27
+ return Lunar.fromYmd(lunarYear, 1, 1).getSolar().toYmd();
28
+ }
29
+ function buildTable() {
30
+ const table = {};
31
+ for (let y = START_YEAR; y <= END_YEAR; y++)
32
+ table[y] = springFestivalOf(y);
33
+ return table;
34
+ }
35
+ function libVersion() {
36
+ const pkg = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'node_modules', 'lunar-typescript', 'package.json'), 'utf-8'));
37
+ return pkg.version;
38
+ }
39
+ function renderBlock(table, generatedAt) {
40
+ const entries = Object.keys(table).map(y => ` ${y}: '${table[Number(y)]}',`).join('\n');
41
+ return `/**
42
+ * 春节锚点表(正月初一;仅此节日漂移大,故表驱动;元旦/国庆固定月日按「下一次发生」算)。
43
+ * 本块由构建期生成器机械生成,禁止手改(issue #274:手抄常量表=换个日子的同款债务,已否决)。
44
+ * provenance:lunar-typescript@${libVersion()}(MIT,零传递依赖,devDependency 不进 runtime)· 生成时间 ${generatedAt}
45
+ * 命令:cd ts && npx tsx scripts/gen-lunar-anchors.ts(扩表/换库=重跑;--check=漂移闸,run-all §58)
46
+ * 交叉验证:2026-2031 旧表 6 条 + 港天文台 2032-2040 核实 9 条逐条一致(time-eval §7 双 oracle)。
47
+ * 到期守卫:springFestivalHorizonOk——最晚锚点年份 < 当前年+3 测试即红(临近耗尽先红于静默缺失)。
48
+ */
49
+ ${BEGIN}
50
+ export const SPRING_FESTIVAL: Record<number, string> = {
51
+ ${entries}
52
+ }
53
+ ${END}`;
54
+ }
55
+ /** 提取文件中现有生成块;无标记则匹配旧手抄声明(含其上一行单行 doc 注释)原位迁移。 */
56
+ function extractExisting(src) {
57
+ const b = src.indexOf(BEGIN);
58
+ const e = src.indexOf(END);
59
+ if (b !== -1 && e !== -1) {
60
+ const end = e + END.length;
61
+ return { block: src.slice(b, end), replaceFrom: next => src.slice(0, b) + next + src.slice(end) };
62
+ }
63
+ const legacy = src.match(/(?:\/\*\*[^\n]*\*\/\n)?(?:export )?const SPRING_FESTIVAL: Record<number, string> = \{\n(?: \d{4}: '\d{4}-\d{2}-\d{2}',\n)+\}/);
64
+ if (!legacy)
65
+ throw new Error('未找到 SPRING_FESTIVAL 表(既无生成标记也无手抄声明)');
66
+ const m = legacy[0];
67
+ return { block: null, replaceFrom: next => src.replace(m, next) };
68
+ }
69
+ /** 从块文本解析 年→日期 映射(--check 与幂等判定用,生成时间/库版本不参与比较)。 */
70
+ function parseTable(block) {
71
+ const table = {};
72
+ if (!block)
73
+ return table;
74
+ for (const m of block.matchAll(/(\d{4}): '(\d{4}-\d{2}-\d{2})'/g))
75
+ table[Number(m[1])] = m[2];
76
+ return table;
77
+ }
78
+ function tablesEqual(a, b) {
79
+ const ka = Object.keys(a);
80
+ const kb = Object.keys(b);
81
+ return ka.length === kb.length && ka.every(k => a[Number(k)] === b[Number(k)]);
82
+ }
83
+ const check = process.argv.includes('--check');
84
+ const table = buildTable();
85
+ const src = readFileSync(TARGET, 'utf-8');
86
+ const { block, replaceFrom } = extractExisting(src);
87
+ if (block && tablesEqual(parseTable(block), table)) {
88
+ console.log(`lunar-anchors:表已是最新(${START_YEAR}-${END_YEAR},共 ${Object.keys(table).length} 条),无 diff`);
89
+ process.exit(0);
90
+ }
91
+ const next = renderBlock(table, new Date().toISOString());
92
+ if (check) {
93
+ console.error(`lunar-anchors:漂移!${TARGET} 与生成器输出不一致(表内容或生成块缺失),应重跑:cd ts && npx tsx scripts/gen-lunar-anchors.ts`);
94
+ if (block) {
95
+ const have = parseTable(block);
96
+ const want = table;
97
+ for (let y = Math.min(START_YEAR, ...Object.keys(have).map(Number)); y <= END_YEAR; y++) {
98
+ if (have[y] !== want[y])
99
+ console.error(` ${y}: 文件=${have[y] ?? '(缺)'} 生成=${want[y] ?? '(缺)'}`);
100
+ }
101
+ }
102
+ process.exit(1);
103
+ }
104
+ writeFileSync(TARGET, replaceFrom(next));
105
+ const diffYears = Object.keys(table).filter(y => !block || parseTable(block)[Number(y)] !== table[Number(y)]);
106
+ console.log(`lunar-anchors:已回写 ${TARGET}(覆盖 ${START_YEAR}-${END_YEAR},共 ${Object.keys(table).length} 条;本次新增/变更 ${diffYears.length} 条)`);
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Offline geo-atlas loader contract tests (issue #342, mechanism only —
3
+ * default off, zero product callers).
4
+ *
5
+ * Run: cd ts && npx tsx scripts/geo-atlas-tests.ts
6
+ *
7
+ * Fully offline: driven by a small synthetic fixture GeoJSON written to a
8
+ * temp directory (never the committed build output, never the network). The
9
+ * offline claim itself is executed: globalThis.fetch is replaced by a
10
+ * throwing spy for the whole run and asserted to stay at zero calls.
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import { createHash } from 'node:crypto';
14
+ import { readFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
15
+ import { tmpdir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { GEO_ATLAS_MANIFEST_SCHEMA, loadGeoAtlas, lookupById, lookupByName, normalizeAtlasName, } from '../capabilities/geo-atlas.js';
18
+ function sha256(data) {
19
+ return createHash('sha256').update(Buffer.from(data, 'utf8')).digest('hex');
20
+ }
21
+ const SQUARE = [[[[100.0, 21.0], [102.0, 21.0], [102.0, 25.0], [100.0, 25.0], [100.0, 21.0]]]];
22
+ const OTHER_SQUARE = [[[[11.0, 60.0], [13.0, 60.0], [13.0, 62.0], [11.0, 62.0], [11.0, 60.0]]]];
23
+ function feature(properties, coordinates = SQUARE) {
24
+ return { type: 'Feature', properties, geometry: { type: 'MultiPolygon', coordinates } };
25
+ }
26
+ const ADMIN_0_FEATURES = [
27
+ feature({ id: 'CHN', name: 'China', name_zh: '中华人民共和国', admin: 'China', iso_a2: 'CN', iso_a3: 'CHN' }),
28
+ feature({ id: 'NOR', name: 'Norway', name_zh: '挪威', admin: 'Norway', iso_a2: 'NO', iso_a3: 'NOR' }, OTHER_SQUARE),
29
+ feature({ id: 'TL', name: 'Testland', admin: 'Testland', iso_a2: 'TX' }),
30
+ ];
31
+ const ADMIN_1_FEATURES = [
32
+ feature({ id: 'CN-YN', name: 'Yunnan', name_zh: '云南省', admin: 'China', iso_a2: 'CN', adm0_a3: 'CHN' }),
33
+ feature({ id: 'NO-18', name: 'Nordland', admin: 'Norway', iso_a2: 'NO', adm0_a3: 'NOR' }, OTHER_SQUARE),
34
+ feature({ id: 'TL-NR', name: 'Nordland', admin: 'Testland', iso_a2: 'TX', adm0_a3: 'TL' }),
35
+ ];
36
+ const UPSTREAM = { repository: 'https://example.invalid/natural-earth-vector', tag: 'fixture-tag', commit: 'fixture-commit' };
37
+ const LICENSE = 'Natural Earth — public domain (no attribution required)';
38
+ async function writeFixture(dir, options = {}) {
39
+ const versionDir = join(dir, 'fixture-v1');
40
+ await mkdir(versionDir, { recursive: true });
41
+ const admin0Text = options.admin0Text ?? JSON.stringify({ type: 'FeatureCollection', features: ADMIN_0_FEATURES });
42
+ const admin1Text = options.admin1Text ?? JSON.stringify({ type: 'FeatureCollection', features: ADMIN_1_FEATURES });
43
+ await writeFile(join(versionDir, 'admin-0.geojson'), admin0Text, 'utf8');
44
+ await writeFile(join(versionDir, 'admin-1.geojson'), admin1Text, 'utf8');
45
+ const manifest = {
46
+ schemaVersion: GEO_ATLAS_MANIFEST_SCHEMA,
47
+ dataset: 'natural-earth',
48
+ version: 'fixture-v1',
49
+ upstream: UPSTREAM,
50
+ license: { name: LICENSE },
51
+ build: { fetchedAt: '2026-10-02T00:00:00.000Z' },
52
+ artifacts: [
53
+ {
54
+ layer: 'admin-0',
55
+ file: 'admin-0.geojson',
56
+ upstreamUrl: 'https://example.invalid/admin-0.geojson',
57
+ upstreamSha256: 'a'.repeat(64),
58
+ recordCount: 3,
59
+ bytes: Buffer.byteLength(admin0Text),
60
+ sha256: sha256(admin0Text),
61
+ },
62
+ {
63
+ layer: 'admin-1',
64
+ file: 'admin-1.geojson',
65
+ upstreamUrl: 'https://example.invalid/admin-1.geojson',
66
+ upstreamSha256: 'b'.repeat(64),
67
+ recordCount: 3,
68
+ bytes: Buffer.byteLength(admin1Text),
69
+ sha256: sha256(admin1Text),
70
+ },
71
+ ],
72
+ };
73
+ await writeFile(join(versionDir, 'manifest.json'), options.manifestText ?? JSON.stringify(manifest, null, 2), 'utf8');
74
+ return versionDir;
75
+ }
76
+ async function loadOk(dir) {
77
+ const result = await loadGeoAtlas(dir);
78
+ assert.equal(result.status, 'ok', `fixture load must succeed, got ${JSON.stringify(result)}`);
79
+ return result.atlas;
80
+ }
81
+ async function loadErr(dir) {
82
+ const result = await loadGeoAtlas(dir);
83
+ assert.equal(result.status, 'error', `fixture load must fail, got ${JSON.stringify(result)}`);
84
+ return result.reason;
85
+ }
86
+ async function withFixture(name, fn) {
87
+ const root = await mkdtemp(join(tmpdir(), name));
88
+ try {
89
+ await fn(await writeFixture(root));
90
+ }
91
+ finally {
92
+ await rm(root, { recursive: true, force: true });
93
+ }
94
+ }
95
+ const tests = [];
96
+ tests.push({
97
+ name: 'green path: SHA-256 verified load, both layers indexed, provenance asserted',
98
+ fn: async () => {
99
+ await withFixture('geo-atlas-green-', async (versionDir) => {
100
+ const atlas = await loadOk(versionDir);
101
+ assert.equal(atlas.version, 'fixture-v1');
102
+ assert.equal(atlas.recordCount(), 6);
103
+ assert.equal(atlas.recordCount('admin-0'), 3);
104
+ assert.equal(atlas.recordCount('admin-1'), 3);
105
+ assert.equal(atlas.snapshot.dataset, 'natural-earth');
106
+ assert.equal(atlas.snapshot.license, LICENSE);
107
+ assert.deepEqual(atlas.snapshot.upstream, UPSTREAM);
108
+ assert.equal(atlas.snapshot.fetchedAt, '2026-10-02T00:00:00.000Z');
109
+ const hit = lookupByName(atlas, 'China');
110
+ assert.equal(hit.status, 'hit');
111
+ if (hit.status !== 'hit')
112
+ return;
113
+ assert.equal(hit.record.id, 'CHN');
114
+ assert.equal(hit.record.nameZh, '中华人民共和国');
115
+ assert.equal(hit.record.geometry.type, 'MultiPolygon');
116
+ assert.ok(hit.record.geometry.coordinates.length >= 1);
117
+ assert.equal(hit.record.provenance.layer, 'admin-0');
118
+ assert.equal(hit.record.provenance.snapshotFile, 'admin-0.geojson');
119
+ assert.equal(hit.record.provenance.upstreamUrl, 'https://example.invalid/admin-0.geojson');
120
+ const onDisk = await readFile(join(versionDir, 'admin-0.geojson'), 'utf8');
121
+ assert.equal(hit.record.provenance.snapshotSha256, sha256(onDisk));
122
+ assert.equal(hit.snapshot.version, 'fixture-v1');
123
+ });
124
+ },
125
+ });
126
+ tests.push({
127
+ name: 'by-name matches the zh name and normalizes case/whitespace',
128
+ fn: async () => {
129
+ await withFixture('geo-atlas-names-', async (versionDir) => {
130
+ const atlas = await loadOk(versionDir);
131
+ const zh = lookupByName(atlas, '中华人民共和国');
132
+ assert.equal(zh.status, 'hit');
133
+ if (zh.status === 'hit')
134
+ assert.equal(zh.record.id, 'CHN');
135
+ const messy = lookupByName(atlas, ' cHina ');
136
+ assert.equal(messy.status, 'hit');
137
+ assert.equal(normalizeAtlasName(' YUNNAN '), 'yunnan');
138
+ });
139
+ },
140
+ });
141
+ tests.push({
142
+ name: 'by-id lookup hits with the layer provenance of that record',
143
+ fn: async () => {
144
+ await withFixture('geo-atlas-id-', async (versionDir) => {
145
+ const atlas = await loadOk(versionDir);
146
+ const hit = lookupById(atlas, 'CN-YN');
147
+ assert.equal(hit.status, 'hit');
148
+ if (hit.status !== 'hit')
149
+ return;
150
+ assert.equal(hit.record.admin, 'China');
151
+ assert.equal(hit.record.provenance.layer, 'admin-1');
152
+ assert.equal(hit.record.provenance.snapshotFile, 'admin-1.geojson');
153
+ assert.equal(hit.record.provenance.snapshotSha256, sha256(await readFile(join(versionDir, 'admin-1.geojson'), 'utf8')));
154
+ const country = lookupById(atlas, 'chn');
155
+ assert.equal(country.status, 'hit');
156
+ });
157
+ },
158
+ });
159
+ tests.push({
160
+ name: 'same-name place → ambiguous with candidates, never a silent pick; layer filter narrows',
161
+ fn: async () => {
162
+ await withFixture('geo-atlas-ambig-', async (versionDir) => {
163
+ const atlas = await loadOk(versionDir);
164
+ const ambiguous = lookupByName(atlas, 'Nordland');
165
+ assert.equal(ambiguous.status, 'ambiguous');
166
+ if (ambiguous.status !== 'ambiguous')
167
+ return;
168
+ assert.equal(ambiguous.candidates.length, 2);
169
+ const ids = ambiguous.candidates.map((c) => c.id).sort();
170
+ assert.deepEqual(ids, ['NO-18', 'TL-NR']);
171
+ for (const candidate of ambiguous.candidates) {
172
+ assert.equal(candidate.layer, 'admin-1');
173
+ assert.ok(candidate.admin);
174
+ }
175
+ assert.match(ambiguous.note, /clarify/i);
176
+ const narrowed = lookupByName(atlas, 'Nordland', { layer: 'admin-0' });
177
+ assert.equal(narrowed.status, 'unknown');
178
+ });
179
+ },
180
+ });
181
+ tests.push({
182
+ name: 'unknown region → three-valued miss, snapshot provenance still attached',
183
+ fn: async () => {
184
+ await withFixture('geo-atlas-unknown-', async (versionDir) => {
185
+ const atlas = await loadOk(versionDir);
186
+ for (const miss of [lookupByName(atlas, 'Atlantis'), lookupById(atlas, 'XX-XX')]) {
187
+ assert.equal(miss.status, 'unknown');
188
+ if (miss.status !== 'unknown')
189
+ continue;
190
+ assert.equal(miss.snapshot.version, 'fixture-v1');
191
+ assert.match(miss.note, /not proof/);
192
+ }
193
+ const empty = lookupByName(atlas, ' ');
194
+ assert.equal(empty.status, 'unknown');
195
+ });
196
+ },
197
+ });
198
+ tests.push({
199
+ name: 'fail-closed: missing/corrupt/invalid manifest, missing/tampered/corrupt/invalid artifact, duplicate id',
200
+ fn: async () => {
201
+ const base = await mkdtemp(join(tmpdir(), 'geo-atlas-fail-'));
202
+ try {
203
+ const missing = await loadGeoAtlas(join(base, 'no-such-dir'));
204
+ assert.equal(missing.status, 'error');
205
+ const rawManifestDir = join(base, 'raw-manifest');
206
+ await mkdir(rawManifestDir, { recursive: true });
207
+ await writeFile(join(rawManifestDir, 'manifest.json'), '{not json', 'utf8');
208
+ assert.equal(await loadErr(rawManifestDir), 'manifest-corrupt');
209
+ const badSchemaDir = await writeFixture(base, {
210
+ manifestText: JSON.stringify({ schemaVersion: 'something-else', dataset: 'x', version: 'y', artifacts: [] }),
211
+ });
212
+ assert.equal(await loadErr(badSchemaDir), 'manifest-invalid');
213
+ const missingArtifactDir = await writeFixture(base);
214
+ await rm(join(missingArtifactDir, 'admin-1.geojson'));
215
+ assert.equal(await loadErr(missingArtifactDir), 'artifact-missing');
216
+ // Tamper happens AFTER the manifest is on disk: the snapshot file no
217
+ // longer matches its recorded SHA-256 (this is the fail-closed seam).
218
+ const tamperedDir = await writeFixture(base);
219
+ await writeFile(join(tamperedDir, 'admin-1.geojson'), await readFile(join(tamperedDir, 'admin-1.geojson'), 'utf8') + ' ', 'utf8');
220
+ assert.equal(await loadErr(tamperedDir), 'artifact-sha-mismatch');
221
+ const brokenJson = JSON.stringify({ type: 'FeatureCollection', features: ADMIN_1_FEATURES }).slice(0, 40);
222
+ const corruptArtifactDir = await writeFixture(base, { admin1Text: brokenJson });
223
+ assert.equal(await loadErr(corruptArtifactDir), 'artifact-corrupt');
224
+ const shapeDriftText = JSON.stringify({
225
+ type: 'FeatureCollection',
226
+ features: [{ type: 'Feature', properties: { id: 'NO-18', name: 'Nordland' } }, ...ADMIN_1_FEATURES.slice(1)],
227
+ });
228
+ const shapeDriftDir = await writeFixture(base, { admin1Text: shapeDriftText });
229
+ assert.equal(await loadErr(shapeDriftDir), 'artifact-invalid');
230
+ const notACollection = JSON.stringify({ type: 'Feature', geometry: null });
231
+ const notCollectionDir = await writeFixture(base, { admin0Text: notACollection });
232
+ assert.equal(await loadErr(notCollectionDir), 'artifact-invalid');
233
+ const duplicateText = JSON.stringify({
234
+ type: 'FeatureCollection',
235
+ features: [...ADMIN_0_FEATURES, feature({ id: 'CHN', name: 'China Duplicate' })],
236
+ });
237
+ const duplicateDir = await writeFixture(base, { admin0Text: duplicateText });
238
+ assert.equal(await loadErr(duplicateDir), 'duplicate-id');
239
+ }
240
+ finally {
241
+ await rm(base, { recursive: true, force: true });
242
+ }
243
+ },
244
+ });
245
+ tests.push({
246
+ name: 'determinism: two loads of the same root answer identically',
247
+ fn: async () => {
248
+ await withFixture('geo-atlas-det-', async (versionDir) => {
249
+ const first = await loadOk(versionDir);
250
+ const second = await loadOk(versionDir);
251
+ assert.deepEqual(first.snapshot, second.snapshot);
252
+ assert.deepEqual(lookupByName(first, 'Yunnan'), lookupByName(second, 'Yunnan'));
253
+ assert.deepEqual(lookupById(first, 'TL-NR'), lookupById(second, 'TL-NR'));
254
+ });
255
+ },
256
+ });
257
+ // The offline spy wraps the entire suite (installed before any test runs and
258
+ // asserted afterwards): zero network is an executed contract, not a claim.
259
+ const realFetch = globalThis.fetch;
260
+ let fetchCalls = 0;
261
+ globalThis.fetch = ((...args) => {
262
+ fetchCalls += 1;
263
+ throw new Error(`geo-atlas loader must be offline; fetch called with ${JSON.stringify(args[0])}`);
264
+ });
265
+ let failed = 0;
266
+ for (const test of tests) {
267
+ try {
268
+ await test.fn();
269
+ console.log(`ok - ${test.name}`);
270
+ }
271
+ catch (error) {
272
+ failed += 1;
273
+ console.error(`FAIL - ${test.name}`);
274
+ console.error(error);
275
+ }
276
+ }
277
+ globalThis.fetch = realFetch;
278
+ assert.equal(fetchCalls, 0, `loader made ${fetchCalls} network call(s); offline contract violated`);
279
+ if (failed > 0) {
280
+ console.error(`${failed} geo-atlas test(s) failed`);
281
+ process.exit(1);
282
+ }
283
+ console.log(`geo-atlas contract tests: ${tests.length} passed, 0 failed (fetch calls: ${fetchCalls})`);
@@ -0,0 +1,206 @@
1
+ /**
2
+ * gotry-backend 内核离线合同测试(路由精确分发/冲突拒装/404/异常隔离/模块鉴权;
3
+ * 不发真实会话检索——session-search 模块以注入 fake 驱动)。
4
+ */
5
+ import assert from 'node:assert/strict';
6
+ import { closeBackend, createBackendServer } from '../src/backend/kernel.js';
7
+ import { startSessionSearchModule } from '../src/backend/modules/session-search.js';
8
+ import { resolveGotryBackendPort } from '../src/gotry-backend.js';
9
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
10
+ let pass = 0;
11
+ let fail = 0;
12
+ const check = (cond, label) => {
13
+ if (cond) {
14
+ pass += 1;
15
+ console.log(` ok - ${label}`);
16
+ }
17
+ else {
18
+ fail += 1;
19
+ console.log(` FAIL - ${label}`);
20
+ }
21
+ };
22
+ async function jfetch(url, init) {
23
+ const r = await fetch(url, init);
24
+ let body = null;
25
+ try {
26
+ body = await r.json();
27
+ }
28
+ catch { /* 空体 */ }
29
+ return { status: r.status, body };
30
+ }
31
+ async function main() {
32
+ check(resolveGotryBackendPort({}) === 3082, '统一入口未配置端口时默认 3082');
33
+ check(resolveGotryBackendPort({ GOTRY_BACKEND_PORT: '0' }) === 0, '统一入口显式端口 0 保留为临时监听');
34
+ check(resolveGotryBackendPort({ GOTRY_BACKEND_PORT: '3083', GOTRY_BOOKING_COPILOT_PORT: '3999' }) === 3083, '统一入口端口优先级稳定');
35
+ for (const invalid of ['-1', '1.5', '65536', 'not-a-port']) {
36
+ assert.throws(() => resolveGotryBackendPort({ GOTRY_BACKEND_PORT: invalid }), /gotry_backend_invalid_port/);
37
+ pass += 1;
38
+ }
39
+ // M1 内核:路由精确分发 + 未知 404 + 异常隔离 500(进程不倒)
40
+ const okModule = {
41
+ name: 'fake-ok',
42
+ routes: [{ method: 'GET', path: '/m1/ping', handle: (_req, res) => { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ pong: true })); } }],
43
+ };
44
+ const boomModule = {
45
+ name: 'fake-boom',
46
+ routes: [{ method: 'GET', path: '/m2/boom', handle: async () => { throw new Error('boom'); } }],
47
+ };
48
+ const session = startSessionSearchModule({
49
+ apiKey: () => 'test-key',
50
+ search: async () => ({
51
+ ok: true, via: 'session-dida-portal', evidence: '[会话:dida-portal@test]', latencyMs: 1, verdict: 'hit',
52
+ rates: [{ hotelName: 'T', roomName: 'R', ratePlanId: 'RP', price: 100, currency: 'CNY', referenceNo: 'REF' }],
53
+ }),
54
+ });
55
+ const handle = await createBackendServer({ modules: [okModule, boomModule, session], port: 0 });
56
+ const base = `http://127.0.0.1:${handle.port}`;
57
+ try {
58
+ const ping = await jfetch(`${base}/m1/ping`);
59
+ check(ping.status === 200 && ping.body.pong === true, '模块路由精确分发');
60
+ const nf = await jfetch(`${base}/nope`);
61
+ check(nf.status === 404 && nf.body.error?.code === 'not_found', '未知路由 404');
62
+ const boom = await jfetch(`${base}/m2/boom`);
63
+ check(boom.status === 500 && boom.body.error?.code === 'gotry_backend_module_error', '模块异常隔离 500(服务面不倒)');
64
+ const afterBoom = await jfetch(`${base}/m1/ping`);
65
+ check(afterBoom.status === 200, '异常后服务面存活');
66
+ check(handle.moduleNames.includes('session-search') && handle.moduleNames.includes('fake-ok'), '模块注册表名单');
67
+ // M2 session-search 模块:鉴权 fail-closed + 注入 fake 检索合同
68
+ const noAuth = await jfetch(`${base}/v1/session/search`, { method: 'POST', body: '{"supplier":"dida-portal"}' });
69
+ check(noAuth.status === 403, '无 token 403');
70
+ const badSupplier = await jfetch(`${base}/v1/session/search`, {
71
+ method: 'POST',
72
+ headers: { authorization: 'Bearer test-key', 'content-type': 'application/json' },
73
+ body: '{"supplier":"ctrip-flight"}',
74
+ });
75
+ check(badSupplier.status === 400, '未知供应商通道 400');
76
+ const hit = await jfetch(`${base}/v1/session/search`, {
77
+ method: 'POST',
78
+ headers: { authorization: 'Bearer test-key', 'content-type': 'application/json' },
79
+ body: '{"supplier":"dida-portal"}',
80
+ });
81
+ const hitBody = hit.body;
82
+ check(hit.status === 200 && hitBody.verdict === 'hit' && hitBody.rates?.[0]?.ratePlanId === 'RP', '注入 fake 检索:结果合同透传');
83
+ // M2b needs-extension:安装入口是该 verdict 唯一的行动项,hotel-fe#3611
84
+ const STORE_URL = 'https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd';
85
+ const extModule = startSessionSearchModule({
86
+ apiKey: () => 'test-key',
87
+ search: async () => ({
88
+ ok: false,
89
+ via: 'session-dida-portal-error',
90
+ evidence: '[会话:dida-portal-needs-extension@test]',
91
+ latencyMs: 1,
92
+ verdict: 'needs-extension',
93
+ error: 'GoTry Session Bridge 扩展未连接',
94
+ installUrl: STORE_URL,
95
+ installAction: 'add-to-chrome',
96
+ }),
97
+ });
98
+ const extHandle = await createBackendServer({ modules: [extModule], port: 0 });
99
+ try {
100
+ const ext = await jfetch(`http://127.0.0.1:${extHandle.port}/v1/session/search`, {
101
+ method: 'POST',
102
+ headers: { authorization: 'Bearer test-key', 'content-type': 'application/json' },
103
+ body: '{"supplier":"dida-portal"}',
104
+ });
105
+ const extBody = ext.body;
106
+ check(ext.status === 200 && extBody.verdict === 'needs-extension', 'needs-extension verdict 透传');
107
+ check(extBody.installUrl === STORE_URL, '安装链接随 verdict 透出');
108
+ check(extBody.installAction === 'add-to-chrome', '安装动作随 verdict 透出');
109
+ }
110
+ finally {
111
+ await closeBackend(extHandle, [extModule]).catch(() => { });
112
+ }
113
+ // M3 #483:POST search/login 输入守卫。submit / search 注入计数证明
114
+ // 被拦截请求到达底层 = 0;/m1/ping 作 liveness 探针验证内核未被
115
+ // unhandledRejection 拖垮。
116
+ const submitCalls = { count: 0 };
117
+ const fakeQueue = {
118
+ extensionConnected: () => true,
119
+ port: 0,
120
+ close: async () => undefined,
121
+ handleMountedRequest: () => undefined,
122
+ submit: async (job) => {
123
+ submitCalls.count += 1;
124
+ submitCalls.lastJob = job;
125
+ return { ok: true, result: { ok: true, opened: true } };
126
+ },
127
+ };
128
+ let searchCalls = 0;
129
+ const guardSession = startSessionSearchModule({
130
+ apiKey: () => 'test-key',
131
+ search: async () => {
132
+ searchCalls += 1;
133
+ return {
134
+ ok: true, via: 'session-dida-portal', evidence: '[会话:dida-portal@test]', latencyMs: 1, verdict: 'hit',
135
+ rates: [{ hotelName: 'T', roomName: 'R', ratePlanId: 'RP', price: 100, currency: 'CNY', referenceNo: 'REF' }],
136
+ };
137
+ },
138
+ jobQueue: fakeQueue,
139
+ });
140
+ const guardHandle = await createBackendServer({ modules: [okModule, guardSession], port: 0 });
141
+ const guardBase = `http://127.0.0.1:${guardHandle.port}`;
142
+ const authHeader = { authorization: 'Bearer test-key', 'content-type': 'application/json' };
143
+ try {
144
+ const rejectionCases = [
145
+ { label: 'search body=null', path: '/v1/session/search', body: 'null', expectError: 'body 必须是 JSON 对象' },
146
+ { label: 'search body=[]', path: '/v1/session/search', body: '[]', expectError: 'body 必须是 JSON 对象' },
147
+ { label: 'search body=number', path: '/v1/session/search', body: '42', expectError: 'body 必须是 JSON 对象' },
148
+ { label: 'search body=string', path: '/v1/session/search', body: '"x"', expectError: 'body 必须是 JSON 对象' },
149
+ { label: 'search body=bool', path: '/v1/session/search', body: 'true', expectError: 'body 必须是 JSON 对象' },
150
+ { label: 'search supplier=number', path: '/v1/session/search', body: '{"supplier":123}', expectError: 'supplier 必须是字符串' },
151
+ { label: 'search supplier=null', path: '/v1/session/search', body: '{"supplier":null}', expectError: 'supplier 必须是字符串' },
152
+ { label: 'search query=string', path: '/v1/session/search', body: '{"supplier":"dida-portal","query":"foo"}', expectError: 'body 必须是 JSON 对象' },
153
+ { label: 'search query.timeoutMs=string', path: '/v1/session/search', body: '{"supplier":"dida-portal","query":{"timeoutMs":"30"}}', expectError: 'body 必须是 JSON 对象' },
154
+ { label: 'search query.timeoutMs=Infinity', path: '/v1/session/search', body: '{"supplier":"dida-portal","query":{"timeoutMs":1e309}}', expectError: 'body 必须是 JSON 对象' },
155
+ { label: 'login body=null', path: '/v1/session/login/open', body: 'null', expectError: 'body 必须是 JSON 对象' },
156
+ { label: 'login url=number', path: '/v1/session/login/open', body: '{"supplier":"dida-portal","url":123}', expectError: 'url 必须是字符串' },
157
+ { label: 'login supplier=bool', path: '/v1/session/login/open', body: '{"supplier":true}', expectError: 'supplier 必须是字符串' },
158
+ { label: 'search 非法 JSON', path: '/v1/session/search', body: '{not-json', expectError: 'body 不是 JSON' },
159
+ { label: 'login 非法 JSON', path: '/v1/session/login/open', body: '{not-json', expectError: 'body 不是 JSON' },
160
+ ];
161
+ for (const c of rejectionCases) {
162
+ const r = await jfetch(`${guardBase}${c.path}`, { method: 'POST', headers: authHeader, body: c.body });
163
+ const err = r.body?.error ?? '';
164
+ check(r.status === 400, `${c.label}:HTTP 400`);
165
+ check(err === c.expectError, `${c.label}:error 稳定(${c.expectError})`);
166
+ const live = await jfetch(`${guardBase}/m1/ping`);
167
+ check(live.status === 200, `${c.label}:拒后 server 存活`);
168
+ }
169
+ // 超 64KB 触发 readBody 拒绝;确保不抛错 / 进程不倒。
170
+ const oversize = 'x'.repeat(64 * 1024 + 16);
171
+ const rOversize = await jfetch(`${guardBase}/v1/session/search`, { method: 'POST', headers: authHeader, body: oversize });
172
+ check(rOversize.status === 400 && (rOversize.body?.error ?? '') === 'body 读取失败', 'search 超 64KB:HTTP 400 body 读取失败');
173
+ const liveOversize = await jfetch(`${guardBase}/m1/ping`);
174
+ check(liveOversize.status === 200, 'search 超 64KB:拒后 server 存活');
175
+ check(searchCalls === 0, '所有被拒 search:search 注入 0 次调用');
176
+ check(submitCalls.count === 0, '所有被拒 login:bridge.submit 0 次调用');
177
+ // 合法路径需真正穿过守卫、调用底层
178
+ const validSearch = await jfetch(`${guardBase}/v1/session/search`, {
179
+ method: 'POST', headers: authHeader, body: '{"supplier":"dida-portal","query":{"entryUrl":"https://portal.dida.com/find","timeoutMs":5000}}',
180
+ });
181
+ check(validSearch.status === 200, '合法 search(query 对象含 entryUrl/timeoutMs):200');
182
+ check(searchCalls === 1, '合法 search:search 注入 +1 次');
183
+ const validLogin = await jfetch(`${guardBase}/v1/session/login/open`, {
184
+ method: 'POST', headers: authHeader, body: '{"supplier":"dida-portal","url":"https://portal.dida.com/login"}',
185
+ });
186
+ check(validLogin.status === 200, '合法 login/open:200');
187
+ check(submitCalls.count === 1 && submitCalls.lastJob?.kind === 'open-login', '合法 login:bridge.submit +1 次且 kind=open-login');
188
+ const offDomain = await jfetch(`${guardBase}/v1/session/login/open`, {
189
+ method: 'POST', headers: authHeader, body: '{"supplier":"dida-portal","url":"https://evil.example.com/login"}',
190
+ });
191
+ check(offDomain.status === 400 && (offDomain.body?.error ?? '').includes('portal.dida.com'), 'login url 域外 400(fail-closed)');
192
+ check(submitCalls.count === 1, 'login url 域外:bridge.submit 未增');
193
+ }
194
+ finally {
195
+ await closeBackend(guardHandle, [okModule, guardSession]);
196
+ }
197
+ }
198
+ finally {
199
+ await closeBackend(handle, [okModule, boomModule, session]).catch(() => { });
200
+ }
201
+ await sleep(50);
202
+ console.log(`\nGOTRY BACKEND KERNEL: ${pass} pass, ${fail} fail`);
203
+ if (fail > 0)
204
+ process.exitCode = 1;
205
+ }
206
+ void main();