@danceiny/gotry 0.0.1-rc.8 → 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 (506) hide show
  1. package/README.md +172 -217
  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 -49
  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 +968 -0
  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 +126 -0
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
  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 +54 -0
  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 +50 -0
  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 +107 -0
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +109 -0
  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 +686 -0
  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 +72 -0
  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 +68 -10
  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 +67 -0
  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 +480 -0
  191. package/dist/scripts/ledger-workflow-crash.js +32 -0
  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 +49 -0
  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 +29 -31
  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 +43 -42
  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 +31 -0
  227. package/dist/scripts/session-attach-poc.js +62 -0
  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 +60 -0
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +29 -0
  233. package/dist/scripts/session-tests.js +862 -0
  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 +703 -219
  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 +261 -0
  253. package/dist/scripts/state-cli.js +585 -0
  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 +59 -0
  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 +86 -0
  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 +2305 -731
  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 +358 -162
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +47 -0
  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 +767 -0
  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 +59 -0
  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 +538 -360
  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 +296 -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 +2107 -224
  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 +278 -40
  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 +360 -89
  506. package/ts/src/wish-pool.ts +79 -0
@@ -0,0 +1,697 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** Typed CheckAvail budget. This module never parses prose or retries a supplier. */
3
+ export const MAX_HOTELS_PER_TASK = 5;
4
+ export const MAX_OFFERS_PER_HOTEL_GENERATION = 3;
5
+ export const MAX_OFFER_CHECKS_PER_HOTEL = 2;
6
+ export const MAX_OFFER_QUERIES_PER_HOTEL = 2;
7
+ function unique(values) { return [...new Set(values)]; }
8
+ export function assertWorkspaceLoadedOfferRefsUnique(workspace) {
9
+ if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer) => offer.offerRef)).size)
10
+ throw new Error('availability_duplicate_offer_ref');
11
+ if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer) => offer.offerVersionRef)).size)
12
+ throw new Error('availability_duplicate_offer_version_ref');
13
+ }
14
+ function stable(value) { if (Array.isArray(value))
15
+ return value.map(stable); if (!value || typeof value !== 'object')
16
+ return value; return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])])); }
17
+ export function digest(value) { return createHash('sha256').update(JSON.stringify(stable(value))).digest('hex'); }
18
+ function workspaceDigest(workspace) { const { contextRef: _context, ...bound } = workspace; return digest(bound); }
19
+ function offerRefsForHotel(workspace, hotelRef, selectedOfferRef) {
20
+ const refs = unique(workspace.loadedOffers.filter((offer) => offer.hotelRef === hotelRef).map((offer) => offer.offerRef));
21
+ if (selectedOfferRef)
22
+ return unique([selectedOfferRef, ...refs]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION);
23
+ if (refs.length > MAX_OFFERS_PER_HOTEL_GENERATION)
24
+ throw new Error('availability_offer_limit_exceeded');
25
+ return refs;
26
+ }
27
+ function loadedOfferVersion(workspace, offerRef, offerVersionRef) { return workspace.loadedOffers.find((offer) => offer.offerRef === offerRef && offer.offerVersionRef === offerVersionRef); }
28
+ function legalOfferRefsForHotel(workspace, hotel, refs) {
29
+ const legal = refs.filter((ref) => {
30
+ if (hotel.tombstonedOfferRefs.includes(ref))
31
+ return false;
32
+ const loaded = workspace.loadedOffers.find((offer) => offer.offerRef === ref && offer.hotelRef === hotel.hotelRef);
33
+ return Boolean(loaded && !hotel.tombstonedOfferVersionRefs.includes(loaded.offerVersionRef));
34
+ });
35
+ if (legal.length > MAX_OFFERS_PER_HOTEL_GENERATION)
36
+ throw new Error('availability_offer_limit_exceeded');
37
+ return unique(legal);
38
+ }
39
+ function copyHotel(hotel) {
40
+ return { ...hotel, currentOfferRefs: [...hotel.currentOfferRefs], invalidatedOfferRefs: [...hotel.invalidatedOfferRefs], tombstonedOfferRefs: [...hotel.tombstonedOfferRefs], tombstonedOfferVersionRefs: [...hotel.tombstonedOfferVersionRefs], ...(hotel.currentGeneration ? { currentGeneration: { ...hotel.currentGeneration, orderedOfferRefs: [...hotel.currentGeneration.orderedOfferRefs] } } : {}) };
41
+ }
42
+ function copyState(state) {
43
+ return { ...state, ...(state.criteria ? { criteria: structuredClone(state.criteria) } : {}), hotelRefs: [...state.hotelRefs], hotels: Object.fromEntries(Object.entries(state.hotels).map(([ref, hotel]) => [ref, copyHotel(hotel)])), attempts: state.attempts.map((attempt) => ({ ...attempt })), queryReservations: state.queryReservations.map((query) => ({ ...query, hotelRefs: [...query.hotelRefs] })), ...(state.lastQueryHotelRefs ? { lastQueryHotelRefs: [...state.lastQueryHotelRefs] } : {}), ...(state.lastQueryOfferHotels ? { lastQueryOfferHotels: { ...state.lastQueryOfferHotels } } : {}), ...(state.terminal ? { terminal: { ...state.terminal, hotelRefs: [...state.terminal.hotelRefs] } } : {}) };
44
+ }
45
+ function isPlainRecord(value) { return Boolean(value && typeof value === 'object' && !Array.isArray(value)); }
46
+ function isCriterion(value, valueCheck) {
47
+ return isPlainRecord(value) && Object.keys(value).length === 2 && (value.strength === 'must' || value.strength === 'prefer') && valueCheck(value.value);
48
+ }
49
+ function isMoney(value) {
50
+ return isPlainRecord(value) && Object.keys(value).length === 3 && typeof value.amount === 'string' && Boolean(value.amount) && typeof value.currency === 'string' && Boolean(value.currency) && typeof value.sourceFactRef === 'string' && Boolean(value.sourceFactRef);
51
+ }
52
+ function isOfferCriteria(value) {
53
+ if (!isPlainRecord(value))
54
+ return false;
55
+ const allowed = ['roomType', 'bedType', 'meals', 'freeCancellation', 'freeCancellationUntil', 'totalPriceMax', 'roomsAvailableMin', 'payAtProperty', 'mobileRate', 'targetCount', 'sort'];
56
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
57
+ return false;
58
+ const strings = (candidate) => Array.isArray(candidate) && candidate.every((item) => typeof item === 'string' && Boolean(item));
59
+ if (value.roomType !== undefined && !isCriterion(value.roomType, strings))
60
+ return false;
61
+ if (value.bedType !== undefined && !isCriterion(value.bedType, strings))
62
+ return false;
63
+ if (value.meals !== undefined && !isCriterion(value.meals, strings))
64
+ return false;
65
+ if (value.freeCancellation !== undefined && !isCriterion(value.freeCancellation, (candidate) => typeof candidate === 'boolean'))
66
+ return false;
67
+ if (value.freeCancellationUntil !== undefined && !isCriterion(value.freeCancellationUntil, (candidate) => typeof candidate === 'string' && Boolean(candidate)))
68
+ return false;
69
+ if (value.totalPriceMax !== undefined && !isCriterion(value.totalPriceMax, isMoney))
70
+ return false;
71
+ if (value.roomsAvailableMin !== undefined && !isCriterion(value.roomsAvailableMin, (candidate) => Number.isSafeInteger(candidate) && candidate >= 0))
72
+ return false;
73
+ if (value.payAtProperty !== undefined && !isCriterion(value.payAtProperty, (candidate) => typeof candidate === 'boolean'))
74
+ return false;
75
+ if (value.mobileRate !== undefined && !isCriterion(value.mobileRate, (candidate) => typeof candidate === 'boolean'))
76
+ return false;
77
+ if (value.targetCount !== undefined && (!Number.isSafeInteger(value.targetCount) || value.targetCount < 0))
78
+ return false;
79
+ if (value.sort !== undefined && !['best_match', 'total_price_asc', 'cancellation_latest'].includes(value.sort))
80
+ return false;
81
+ return true;
82
+ }
83
+ function bindCriteria(next, criteriaDigest, criteria, required = false) {
84
+ if (criteria !== undefined) {
85
+ if (!isOfferCriteria(criteria) || digest(criteria) !== criteriaDigest)
86
+ throw new Error('availability_criteria_invalid');
87
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest)
88
+ throw new Error('availability_criteria_changed');
89
+ if (next.criteria && digest(next.criteria) !== criteriaDigest)
90
+ throw new Error('availability_criteria_changed');
91
+ next.criteria = structuredClone(criteria);
92
+ next.criteriaDigest = criteriaDigest;
93
+ }
94
+ else if (required && !next.criteria)
95
+ throw new Error('availability_criteria_required');
96
+ else if (next.criteria && next.criteriaDigest !== digest(next.criteria))
97
+ throw new Error('availability_criteria_mismatch');
98
+ else if (!next.criteria && criteriaDigest) {
99
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest)
100
+ throw new Error('availability_criteria_changed');
101
+ next.criteriaDigest = criteriaDigest;
102
+ }
103
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest && criteriaDigest)
104
+ throw new Error('availability_criteria_changed');
105
+ }
106
+ function terminalFor(state) {
107
+ if (!state.recoveryStarted || !state.hotelRefs.length)
108
+ return undefined;
109
+ const hotels = state.hotelRefs.map((ref) => state.hotels[ref]);
110
+ if (hotels.some((hotel) => hotel.status === 'confirmed'))
111
+ return { code: 'availability_confirmed', hotelRefs: [...state.hotelRefs], reason: 'confirmed', evidence: 'conclusive' };
112
+ // A negative result is conclusive only when it has no legal refresh left.
113
+ // The first manual check starts with a workspace generation (zero recovery
114
+ // queries), so it must leave a fresh-query transition available.
115
+ if (hotels.some((hotel) => hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL
116
+ && (hotel.status === 'active'
117
+ || hotel.status === 'unvisited'
118
+ || (hotel.freshOffersRequired && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL))))
119
+ return undefined;
120
+ const conclusive = hotels.every((hotel) => hotel.status === 'negative');
121
+ return { code: conclusive ? 'availability_exhausted_complete' : 'availability_exhausted_inconclusive', hotelRefs: [...state.hotelRefs], reason: hotels.every((hotel) => !hotel.currentOfferRefs.length) ? 'no_current_offers' : 'check_limit_reached', evidence: conclusive ? 'conclusive' : 'inconclusive' };
122
+ }
123
+ function withTerminal(state) { const terminal = terminalFor(state); return terminal ? { ...state, terminal, availabilityPhase: 'terminal' } : state; }
124
+ function newHotel(hotelRef) { return { hotelRef, status: 'unvisited', checksIssued: 0, offerQueriesIssued: 0, generationNo: 0, generation: 0, currentOfferRefs: [], invalidatedOfferRefs: [], checkCount: 0, freshOffersRequired: false, tombstonedOfferRefs: [], tombstonedOfferVersionRefs: [], lastEvidence: 'none' }; }
125
+ function phaseForActiveHotel(state) {
126
+ const active = state.hotels[state.hotelRefs[state.activeHotelOrdinal] ?? ''];
127
+ return active?.status === 'active' && active.currentGeneration?.valid && active.currentOfferRefs.length > 0 && !active.freshOffersRequired
128
+ ? 'need_check'
129
+ : 'need_offers';
130
+ }
131
+ /** Initial workspace is only a hint; the bounded recovery starts at typed offers.query. */
132
+ export function createAvailabilityPolicy(workspace) { assertWorkspaceLoadedOfferRefsUnique(workspace); return { initialized: false, recoveryStarted: false, availabilityPhase: 'need_offers', activeHotelOrdinal: 0, hotelRefs: [], hotels: {}, attempts: [], queryReservations: [] }; }
133
+ /** Ordinary search results never get silently truncated into recovery candidates. */
134
+ export function recordVisibleHotels(state, _workspace) { return copyState(state); }
135
+ /** Observe a normal offers query without opening an availability recovery. */
136
+ export function recordObservedOffersQuery(state, hotelRefs, criteriaDigest, workspace, receipt, sourceActionId, receiptDigest, criteria) {
137
+ if (workspace)
138
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
139
+ if (state.recoveryStarted)
140
+ return copyState(state);
141
+ const next = copyState(state);
142
+ if (!workspace || !receipt)
143
+ return next;
144
+ delete next.lastQueryHotelRefs;
145
+ delete next.lastQueryCriteriaDigest;
146
+ delete next.lastQueryOfferHotels;
147
+ delete next.lastQuerySourceActionId;
148
+ delete next.lastQuerySourceReceiptDigest;
149
+ delete next.lastQueryWorkspaceRevision;
150
+ const completeReceipt = receipt?.status === 'applied'
151
+ && receipt.resultContract.outcome === 'complete'
152
+ && receipt.resultContract.hardCriteriaMet
153
+ && !receipt.resultContract.gapCodes.length
154
+ && !receipt.resultContract.blockers.length
155
+ && receipt.observation.kind === 'offers.state'
156
+ && !receipt.observation.gapCodes?.length;
157
+ if (completeReceipt && receipt.observation.kind === 'offers.state') {
158
+ if (!criteria)
159
+ throw new Error('availability_criteria_required');
160
+ // Ordinary queries establish the latest recovery seed. Before recovery
161
+ // starts, a newer complete query is allowed to replace an older seed.
162
+ delete next.criteria;
163
+ delete next.criteriaDigest;
164
+ bindCriteria(next, criteriaDigest, criteria);
165
+ if (!sourceActionId || sourceActionId !== receipt.actionId || !receiptDigest || receiptDigest !== digest(receipt))
166
+ throw new Error('availability_query_provenance_mismatch');
167
+ next.lastQueryHotelRefs = [...new Set(hotelRefs)];
168
+ next.lastQueryCriteriaDigest = criteriaDigest || undefined;
169
+ next.lastQueryOfferHotels = validateOffersReceiptWorkspace(receipt, workspace, hotelRefs);
170
+ next.lastQuerySourceActionId = sourceActionId;
171
+ next.lastQuerySourceReceiptDigest = receiptDigest;
172
+ next.lastQueryWorkspaceRevision = workspace.revision;
173
+ }
174
+ else {
175
+ // The latest ordinary query is the only candidate provenance. A failed
176
+ // or partial query must not leave an older criteria object usable for a
177
+ // later manual recovery.
178
+ delete next.criteria;
179
+ delete next.criteriaDigest;
180
+ }
181
+ return next;
182
+ }
183
+ /**
184
+ * Reconcile an offers receipt against the post-action workspace before its
185
+ * provenance can influence recovery. Complete receipts must describe exactly
186
+ * the authoritative loaded offers; partial/gap receipts may only describe an
187
+ * evidenced subset. An unreported offer is never silently admitted.
188
+ */
189
+ export function validateOffersReceiptWorkspace(receipt, workspace, hotelRefs) {
190
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
191
+ if (receipt.observation.kind !== 'offers.state')
192
+ throw new Error('availability_offers_observation_required');
193
+ const requested = unique(hotelRefs);
194
+ if (!sameSet(receipt.observation.hotelRefs, requested))
195
+ throw new Error('availability_offers_hotel_mismatch');
196
+ const authoritative = workspace.loadedOffers.filter((offer) => requested.includes(offer.hotelRef));
197
+ const authoritativeRefs = unique(authoritative.map((offer) => offer.offerRef));
198
+ const observedRefs = unique(receipt.observation.offerRefs);
199
+ if (observedRefs.some((offerRef) => !authoritativeRefs.includes(offerRef)))
200
+ throw new Error('availability_offers_unreported');
201
+ const authoritativeHotels = new Set(authoritative.map((offer) => offer.hotelRef)).size;
202
+ const observedOfferHotels = Object.fromEntries(observedRefs.map((offerRef) => [offerRef, authoritative.find((offer) => offer.offerRef === offerRef).hotelRef]));
203
+ const observedHotels = new Set(Object.values(observedOfferHotels)).size;
204
+ const clean = !receipt.resultContract.gapCodes.length && !receipt.resultContract.blockers.length && !receipt.observation.gapCodes?.length;
205
+ if ((receipt.status === 'no_match' || receipt.status === 'unavailable') && (receipt.resultContract.outcome !== 'empty' || observedRefs.length !== 0 || receipt.observation.loadedHotelCount !== 0))
206
+ throw new Error('availability_offers_receipt_incoherent');
207
+ const complete = receipt.status === 'applied' && receipt.resultContract.outcome === 'complete' && receipt.resultContract.hardCriteriaMet && clean;
208
+ const exactEmpty = clean && receipt.resultContract.outcome === 'empty' && !['changed', 'partial', 'stale', 'failed', 'unsupported'].includes(receipt.status);
209
+ if ((complete || exactEmpty) && (!sameSet(observedRefs, authoritativeRefs) || receipt.observation.loadedHotelCount !== authoritativeHotels))
210
+ throw new Error('availability_offers_workspace_mismatch');
211
+ if (receipt.observation.loadedHotelCount !== observedHotels)
212
+ throw new Error('availability_offers_count_mismatch');
213
+ return observedOfferHotels;
214
+ }
215
+ function sameSet(a, b) { return a.length === b.length && unique(a).sort().every((value, index) => value === unique(b).sort()[index]); }
216
+ /** A typed offers.query receipt starts/advances a bounded candidate recovery. */
217
+ export function recordOffersQueryIssued(state, hotelRefs, workspace, actionId, criteriaDigest = '', criteria) {
218
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
219
+ const next = copyState(state);
220
+ const requested = unique(hotelRefs);
221
+ if (!requested.length || requested.length > MAX_HOTELS_PER_TASK)
222
+ throw new Error('availability_hotel_limit_exceeded');
223
+ if (!state.recoveryStarted)
224
+ throw new Error('availability_recovery_not_started');
225
+ if (state.terminal) {
226
+ if (requested.some((ref) => state.hotels[ref]?.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL))
227
+ throw new Error('availability_offer_query_limit_reached');
228
+ throw new Error('availability_terminal');
229
+ }
230
+ if (requested.some((ref) => !next.hotels[ref]))
231
+ throw new Error('availability_hotel_unknown');
232
+ else if ((requested.length !== 1 || next.hotelRefs[next.activeHotelOrdinal] !== requested[0]) && (next.attempts.length > 0 || Object.values(next.hotels).some((hotel) => hotel.generationNo > 0)))
233
+ throw new Error('availability_hotel_not_active');
234
+ bindCriteria(next, criteriaDigest, criteria);
235
+ const existingReservation = next.queryReservations.find((reservation) => reservation.actionId === actionId);
236
+ if (existingReservation) {
237
+ if (!sameSet(existingReservation.hotelRefs, requested) || existingReservation.workspaceRevision !== workspace.revision)
238
+ throw new Error('availability_query_action_conflict');
239
+ return next;
240
+ }
241
+ for (const hotelRef of requested) {
242
+ const hotel = next.hotels[hotelRef];
243
+ if (hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL)
244
+ throw new Error('availability_offer_query_limit_reached');
245
+ hotel.offerQueriesIssued += 1;
246
+ }
247
+ next.queryReservations.push({ actionId, hotelRefs: [...requested], workspaceRevision: workspace.revision });
248
+ next.availabilityPhase = 'waiting_offers';
249
+ next.initialized = true;
250
+ return next;
251
+ }
252
+ export function recordOffersGeneration(state, hotelRefs, workspace, actionId, receiptDigest, receipt, criteriaDigest = '', criteria) {
253
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
254
+ if (!state.recoveryStarted)
255
+ return copyState(state);
256
+ if (state.terminal)
257
+ throw new Error('availability_terminal');
258
+ const next = copyState(state);
259
+ const requested = unique(hotelRefs);
260
+ if (!requested.length || requested.length > MAX_HOTELS_PER_TASK)
261
+ throw new Error('availability_hotel_limit_exceeded');
262
+ const reservation = next.queryReservations.find((query) => query.actionId === actionId);
263
+ if (!reservation || !sameSet(reservation.hotelRefs, requested))
264
+ throw new Error('availability_query_not_reserved');
265
+ if (receipt.actionId !== actionId)
266
+ throw new Error('availability_query_receipt_mismatch');
267
+ if (receiptDigest !== digest(receipt))
268
+ throw new Error('availability_receipt_digest_mismatch');
269
+ if (requested.some((ref) => !next.hotels[ref]))
270
+ throw new Error('availability_hotel_unknown');
271
+ else if ((requested.length !== 1 || next.hotelRefs[next.activeHotelOrdinal] !== requested[0]) && (next.attempts.length > 0 || Object.values(next.hotels).some((hotel) => hotel.generationNo > 0)))
272
+ throw new Error('availability_hotel_not_active');
273
+ bindCriteria(next, criteriaDigest, criteria);
274
+ if (receipt.observation.kind === 'offers.state')
275
+ validateOffersReceiptWorkspace(receipt, workspace, requested);
276
+ for (const hotelRef of requested) {
277
+ const hotel = next.hotels[hotelRef];
278
+ const partial = receipt.status === 'changed' || receipt.status === 'partial' || receipt.status === 'failed' || receipt.status === 'stale' || receipt.resultContract.outcome === 'partial' || (receipt.resultContract.outcome === 'complete' && !receipt.resultContract.hardCriteriaMet) || Boolean(receipt.resultContract.blockers.length) || Boolean(receipt.resultContract.gapCodes.length) || Boolean(receipt.observation.kind === 'gap' || (receipt.observation.kind === 'offers.state' && receipt.observation.gapCodes?.length));
279
+ const candidateRefs = partial
280
+ ? receipt.observation.kind === 'offers.state'
281
+ ? receipt.observation.offerRefs.filter((ref) => workspace.loadedOffers.some((offer) => offer.offerRef === ref && offer.hotelRef === hotelRef))
282
+ : []
283
+ : offerRefsForHotel(workspace, hotelRef);
284
+ const refs = legalOfferRefsForHotel(workspace, hotel, candidateRefs);
285
+ const unusable = ['stale', 'failed', 'unsupported'].includes(receipt.status);
286
+ if (unusable) {
287
+ hotel.currentOfferRefs = [];
288
+ hotel.invalidatedOfferRefs = [...(hotel.currentGeneration?.orderedOfferRefs ?? [])];
289
+ hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
290
+ hotel.status = 'inconclusive';
291
+ hotel.lastEvidence = 'inconclusive';
292
+ continue;
293
+ }
294
+ const filteredAllCandidates = candidateRefs.length > 0 && refs.length === 0;
295
+ const noLegalRefsNeedsRefresh = filteredAllCandidates && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
296
+ hotel.generationNo += 1;
297
+ hotel.generation = hotel.generationNo;
298
+ hotel.currentOfferRefs = partial || noLegalRefsNeedsRefresh ? [] : refs;
299
+ hotel.invalidatedOfferRefs = partial || noLegalRefsNeedsRefresh ? candidateRefs : [];
300
+ hotel.freshOffersRequired = (partial || noLegalRefsNeedsRefresh) && hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
301
+ hotel.status = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'active' : 'negative';
302
+ hotel.lastEvidence = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'none' : 'unavailable';
303
+ hotel.currentGeneration = { generationId: `${hotelRef}:generation:${hotel.generationNo}`, source: { kind: 'query_receipt', actionId, receiptDigest, workspaceRevision: workspace.revision }, offerSetDigest: digest(refs), orderedOfferRefs: refs, evidence: partial ? 'partial' : 'complete', valid: refs.length > 0 && !partial };
304
+ }
305
+ const firstRequested = next.hotelRefs.indexOf(requested[0]);
306
+ if (firstRequested >= 0)
307
+ next.activeHotelOrdinal = firstRequested;
308
+ const queried = next.hotels[requested[0]];
309
+ if (!queried.currentOfferRefs.length && !queried.freshOffersRequired) {
310
+ const fallback = next.hotelRefs.findIndex((ref) => ['active', 'unvisited'].includes(next.hotels[ref].status));
311
+ if (fallback >= 0)
312
+ next.activeHotelOrdinal = fallback;
313
+ }
314
+ next.queryReservations = next.queryReservations.filter((query) => query.actionId !== actionId);
315
+ next.initialized = true;
316
+ next.availabilityPhase = phaseForActiveHotel(next);
317
+ delete next.terminal;
318
+ return withTerminal(next);
319
+ }
320
+ export function canIssueOfferCheck(state, workspace, offerRef, offerVersionRef) {
321
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
322
+ // Availability recovery is opt-in at the typed offers.query seam. Existing
323
+ // v2 read flows retain their pre-recovery offer.check behavior.
324
+ if (!state.recoveryStarted) {
325
+ const loaded = loadedOfferVersion(workspace, offerRef, offerVersionRef);
326
+ return loaded
327
+ ? { ok: true, hotelRef: loaded.hotelRef, offerRef, offerVersionRef, checkCount: 0, generation: 0 }
328
+ : { ok: false, code: 'offer_not_loaded' };
329
+ }
330
+ if (state.terminal)
331
+ return { ok: false, code: 'availability_exhausted' };
332
+ const loaded = loadedOfferVersion(workspace, offerRef, offerVersionRef);
333
+ if (!loaded)
334
+ return { ok: false, code: 'offer_not_loaded' };
335
+ const hotel = state.hotels[loaded.hotelRef];
336
+ if (!hotel)
337
+ return { ok: false, code: 'hotel_unknown' };
338
+ if (state.hotelRefs[state.activeHotelOrdinal] !== loaded.hotelRef)
339
+ return { ok: false, code: 'hotel_not_active' };
340
+ if (hotel.freshOffersRequired)
341
+ return { ok: false, code: 'offers_refresh_required' };
342
+ if (hotel.tombstonedOfferRefs.includes(offerRef) || hotel.tombstonedOfferVersionRefs.includes(offerVersionRef))
343
+ return { ok: false, code: 'generation_invalid' };
344
+ if (!hotel.currentGeneration?.valid || !hotel.currentGeneration.orderedOfferRefs.includes(offerRef) || hotel.invalidatedOfferRefs.includes(offerRef))
345
+ return { ok: false, code: 'generation_invalid' };
346
+ if (hotel.checksIssued >= MAX_OFFER_CHECKS_PER_HOTEL)
347
+ return { ok: false, code: 'hotel_check_limit_reached' };
348
+ return { ok: true, hotelRef: loaded.hotelRef, offerRef, offerVersionRef, checkCount: hotel.checksIssued + 1, generation: hotel.generationNo };
349
+ }
350
+ /** Start the bounded recovery epoch at the first typed offer.check. */
351
+ function startRecoveryAtOffer(state, workspace, offerRef, actionId) {
352
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
353
+ const loaded = workspace.loadedOffers.find((offer) => offer.offerRef === offerRef);
354
+ if (!loaded)
355
+ return copyState(state);
356
+ const shortlistHotels = workspace.shortlistedOfferRefs.map((ref) => workspace.loadedOffers.find((offer) => offer.offerRef === ref)?.hotelRef ?? state.lastQueryOfferHotels?.[ref]).map((ref, index) => {
357
+ if (!ref)
358
+ throw new Error(`availability_shortlist_unmapped:${workspace.shortlistedOfferRefs[index]}`);
359
+ return ref;
360
+ });
361
+ const fallbackHotels = shortlistHotels.length ? shortlistHotels : [];
362
+ const candidates = unique([loaded.hotelRef, ...fallbackHotels]).slice(0, MAX_HOTELS_PER_TASK);
363
+ const next = copyState(state);
364
+ next.recoveryStarted = true;
365
+ next.recoveryId = `recovery:${actionId}`;
366
+ next.hotelRefs = candidates;
367
+ next.candidateSetDigest = digest(candidates);
368
+ next.hotels = Object.fromEntries(candidates.map((ref) => [ref, newHotel(ref)]));
369
+ const nextHotel = next.hotels[loaded.hotelRef];
370
+ const mappedRefs = state.lastQueryOfferHotels ? Object.entries(state.lastQueryOfferHotels).filter(([, hotel]) => hotel === loaded.hotelRef).map(([ref]) => ref) : [];
371
+ const currentRefs = unique(workspace.loadedOffers.filter((offer) => offer.hotelRef === loaded.hotelRef).map((offer) => offer.offerRef));
372
+ const mapped = state.lastQueryOfferHotels?.[offerRef] === loaded.hotelRef
373
+ && state.lastQueryWorkspaceRevision === workspace.revision
374
+ && Boolean(state.lastQuerySourceActionId && state.lastQuerySourceReceiptDigest)
375
+ && sameSet(mappedRefs, currentRefs);
376
+ if (mapped && state.criteria) {
377
+ next.criteria = structuredClone(state.criteria);
378
+ next.criteriaDigest = digest(next.criteria);
379
+ }
380
+ const refs = mapped && state.lastQueryOfferHotels
381
+ ? unique([offerRef, ...mappedRefs]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION)
382
+ : offerRefsForHotel(workspace, loaded.hotelRef, offerRef);
383
+ const source = mapped
384
+ ? { kind: 'query_receipt', actionId: state.lastQuerySourceActionId, receiptDigest: state.lastQuerySourceReceiptDigest, workspaceRevision: state.lastQueryWorkspaceRevision }
385
+ : { kind: 'workspace_snapshot', workspaceDigest: workspaceDigest(workspace), workspaceRevision: workspace.revision };
386
+ nextHotel.generationNo = 1;
387
+ nextHotel.generation = 1;
388
+ nextHotel.currentOfferRefs = refs;
389
+ nextHotel.status = refs.length ? 'active' : 'inconclusive';
390
+ nextHotel.currentGeneration = { generationId: `${loaded.hotelRef}:generation:1`, source, offerSetDigest: digest(refs), orderedOfferRefs: refs, evidence: 'complete', valid: refs.length > 0 };
391
+ next.initialized = true;
392
+ next.availabilityPhase = 'need_check';
393
+ return next;
394
+ }
395
+ export function recordOfferCheckIssued(state, workspace, offerRef, offerVersionRef, actionId = `offer-check-${offerRef}-${state.attempts.length + 1}`) {
396
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
397
+ if (state.terminal)
398
+ throw new Error('availability_terminal');
399
+ if (!state.recoveryStarted) {
400
+ if (!loadedOfferVersion(workspace, offerRef, offerVersionRef))
401
+ throw new Error('availability_offer_not_loaded');
402
+ const started = startRecoveryAtOffer(state, workspace, offerRef, actionId);
403
+ return recordOfferCheckIssued(started, workspace, offerRef, offerVersionRef, actionId);
404
+ }
405
+ const decision = canIssueOfferCheck(state, workspace, offerRef, offerVersionRef);
406
+ if (!decision.ok)
407
+ throw new Error(`availability_${decision.code}`);
408
+ const next = copyState(state);
409
+ const hotel = next.hotels[decision.hotelRef];
410
+ hotel.checksIssued += 1;
411
+ hotel.checkCount = hotel.checksIssued;
412
+ next.attempts.push({ actionId, hotelRef: decision.hotelRef, offerRef, offerVersionRef, generation: hotel.generationNo, ordinal: hotel.checksIssued, workspaceRevision: workspace.revision });
413
+ hotel.currentGeneration.valid = false;
414
+ next.availabilityPhase = 'waiting_check';
415
+ return next;
416
+ }
417
+ /**
418
+ * Fold the availability portion of an issued action. Keeping this reducer
419
+ * next to the receipt reducers makes ledger replay use precisely the same
420
+ * transition as the write path.
421
+ */
422
+ export function reduceAvailabilityAction(state, workspace, action) {
423
+ if (action.kind === 'offers.query') {
424
+ if (!isOfferCriteria(action.input.criteria) && (!state.criteria || !isOfferCriteria(state.criteria)))
425
+ throw new Error('availability_criteria_required');
426
+ if (!isOfferCriteria(action.input.criteria) && state.recoveryStarted)
427
+ throw new Error('availability_criteria_required');
428
+ if (!isOfferCriteria(action.input.criteria))
429
+ throw new Error('availability_criteria_required');
430
+ const criteriaDigest = digest(action.input.criteria);
431
+ let next = recordObservedOffersQuery(state, action.input.hotelRefs, criteriaDigest);
432
+ if (state.recoveryStarted)
433
+ next = recordOffersQueryIssued(next, action.input.hotelRefs, workspace, action.actionId, criteriaDigest, action.input.criteria);
434
+ return next;
435
+ }
436
+ if (action.kind === 'offer.check')
437
+ return recordOfferCheckIssued(state, workspace, action.input.offerRef, action.input.offerVersionRef, action.actionId);
438
+ return copyState(state);
439
+ }
440
+ /** Fold a receipt against the exact durable attempt. Non-confirming results invalidate the whole generation. */
441
+ export function recordOfferCheckReceipt(state, workspace, receipt, actionId, offerRef, offerVersionRef, expectedRevision) {
442
+ assertWorkspaceLoadedOfferRefsUnique(workspace);
443
+ if (!state.recoveryStarted)
444
+ return copyState(state);
445
+ const attempt = state.attempts.find((candidate) => candidate.actionId === actionId);
446
+ if (!attempt)
447
+ throw new Error('availability_attempt_unknown');
448
+ if (receipt.actionId !== actionId)
449
+ throw new Error('availability_attempt_mismatch');
450
+ if (receipt.revision !== workspace.revision || receipt.revision < expectedRevision)
451
+ throw new Error('availability_stale_receipt');
452
+ const targetOfferRef = receipt.observation.kind === 'offer.availability' ? receipt.observation.offerRef : offerRef;
453
+ const targetVersionRef = receipt.observation.kind === 'offer.availability' ? receipt.observation.checkedOfferVersionRef : offerVersionRef;
454
+ if (attempt.offerRef !== targetOfferRef || attempt.offerVersionRef !== targetVersionRef || offerRef !== targetOfferRef || offerVersionRef !== targetVersionRef || attempt.workspaceRevision !== expectedRevision)
455
+ throw new Error('availability_attempt_mismatch');
456
+ const next = copyState(state);
457
+ const hotel = next.hotels[attempt.hotelRef];
458
+ if (!hotel || !hotel.currentGeneration || attempt.generation !== hotel.generationNo)
459
+ throw new Error('availability_generation_mismatch');
460
+ const observation = receipt.observation.kind === 'offer.availability' ? receipt.observation : undefined;
461
+ if (['applied', 'changed', 'unavailable', 'no_match'].includes(receipt.status) && !observation)
462
+ throw new Error('availability_observation_required');
463
+ if (observation?.available && (receipt.resultContract.outcome === 'empty' || ['unavailable', 'no_match', 'failed', 'stale', 'unsupported'].includes(receipt.status)))
464
+ throw new Error('availability_receipt_incoherent');
465
+ if (observation && !observation.available && (observation.verifiedOfferRef || observation.currentOfferVersionRef))
466
+ throw new Error('availability_receipt_incoherent');
467
+ if (['unavailable', 'no_match'].includes(receipt.status) && receipt.resultContract.outcome !== 'empty')
468
+ throw new Error('availability_receipt_incoherent');
469
+ const confirmed = receipt.status === 'applied' && Boolean(observation?.available && observation.currentOfferVersionRef === offerVersionRef && observation.verifiedOfferRef && workspace.verifiedOffer?.offerRef === offerRef && workspace.verifiedOffer.offerVersionRef === offerVersionRef && workspace.verifiedOffer.verifiedOfferRef === observation.verifiedOfferRef && loadedOfferVersion(workspace, offerRef, offerVersionRef)) && !observation?.changedFactRefs.length && !observation?.gapCodes?.length && receipt.resultContract.outcome === 'complete' && receipt.resultContract.hardCriteriaMet && !receipt.resultContract.blockers.length && !receipt.resultContract.gapCodes.length;
470
+ if (confirmed) {
471
+ hotel.status = 'confirmed';
472
+ hotel.lastEvidence = 'confirmed';
473
+ hotel.currentGeneration.valid = false;
474
+ return withTerminal(next);
475
+ }
476
+ if (receipt.status === 'changed' && observation?.available && observation.currentOfferVersionRef && observation.currentOfferVersionRef !== offerVersionRef && observation.changedFactRefs.length && !observation.verifiedOfferRef && loadedOfferVersion(workspace, offerRef, observation.currentOfferVersionRef)) {
477
+ hotel.tombstonedOfferVersionRefs = unique([...hotel.tombstonedOfferVersionRefs, offerVersionRef]);
478
+ hotel.currentGeneration.valid = true;
479
+ hotel.invalidatedOfferRefs = [];
480
+ hotel.currentOfferRefs = [offerRef];
481
+ hotel.freshOffersRequired = false;
482
+ hotel.status = 'active';
483
+ hotel.lastEvidence = 'none';
484
+ next.availabilityPhase = 'need_check';
485
+ return withTerminal(next);
486
+ }
487
+ const completeNegative = (receipt.status === 'unavailable' || receipt.status === 'no_match') && receipt.resultContract.outcome === 'empty' && observation?.available === false && receipt.resultContract.gapCodes.length === 0 && !receipt.resultContract.blockers.length && !observation.gapCodes?.length;
488
+ if (completeNegative) {
489
+ hotel.tombstonedOfferVersionRefs = unique([...hotel.tombstonedOfferVersionRefs, offerVersionRef]);
490
+ hotel.tombstonedOfferRefs = unique([...hotel.tombstonedOfferRefs, offerRef]);
491
+ }
492
+ hotel.currentGeneration.valid = false;
493
+ hotel.invalidatedOfferRefs = [...hotel.currentGeneration.orderedOfferRefs];
494
+ hotel.currentOfferRefs = [];
495
+ hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
496
+ hotel.status = completeNegative ? 'negative' : 'inconclusive';
497
+ hotel.lastEvidence = completeNegative ? 'unavailable' : 'inconclusive';
498
+ const currentOrdinal = next.hotelRefs.indexOf(attempt.hotelRef);
499
+ const nextOrdinal = hotel.freshOffersRequired ? currentOrdinal : next.hotelRefs.findIndex((ref) => ['active', 'unvisited'].includes(next.hotels[ref].status));
500
+ if (nextOrdinal >= 0)
501
+ next.activeHotelOrdinal = nextOrdinal;
502
+ next.availabilityPhase = phaseForActiveHotel(next);
503
+ return withTerminal(next);
504
+ }
505
+ /** Fold the availability portion of a receipt using the same pure reducer as
506
+ * continueWithReceipt and resumeTask. */
507
+ export function reduceAvailabilityReceipt(state, workspace, receipt, action) {
508
+ let next = recordVisibleHotels(state, workspace);
509
+ if (action.kind === 'offers.query' && !state.recoveryStarted) {
510
+ if (!isOfferCriteria(action.input.criteria))
511
+ throw new Error('availability_criteria_required');
512
+ next = recordObservedOffersQuery(next, action.input.hotelRefs, digest(action.input.criteria), workspace, receipt, action.actionId, digest(receipt), action.input.criteria);
513
+ }
514
+ else if (action.kind === 'offers.query' && state.recoveryStarted) {
515
+ if (!isOfferCriteria(action.input.criteria))
516
+ throw new Error('availability_criteria_required');
517
+ next = recordOffersGeneration(next, action.input.hotelRefs, workspace, action.actionId, digest(receipt), receipt, state.criteriaDigest ?? '', action.input.criteria);
518
+ }
519
+ else if (action.kind === 'offer.check') {
520
+ if (action.expectedRevision === undefined)
521
+ throw new Error('availability_expected_revision_missing');
522
+ next = recordOfferCheckReceipt(next, workspace, receipt, action.actionId, action.input.offerRef, action.input.offerVersionRef, action.expectedRevision);
523
+ }
524
+ return next;
525
+ }
526
+ export function availabilityPolicyIsTerminal(state) { return Boolean(state.terminal); }
527
+ export function availabilityPolicyResult(state) { return state.terminal ? { ...state.terminal, hotelRefs: [...state.terminal.hotelRefs] } : undefined; }
528
+ export function validateAvailabilityPolicy(state) {
529
+ try {
530
+ if (!state || typeof state.initialized !== 'boolean' || typeof state.recoveryStarted !== 'boolean')
531
+ return false;
532
+ if (state.recoveryId !== undefined && (typeof state.recoveryId !== 'string' || !state.recoveryId))
533
+ return false;
534
+ if (state.criteriaDigest !== undefined && (typeof state.criteriaDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.criteriaDigest)))
535
+ return false;
536
+ if (state.candidateSetDigest !== undefined && (typeof state.candidateSetDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.candidateSetDigest)))
537
+ return false;
538
+ if (!['need_offers', 'waiting_offers', 'need_check', 'waiting_check', 'terminal'].includes(state.availabilityPhase))
539
+ return false;
540
+ if (!Number.isSafeInteger(state.activeHotelOrdinal) || !Array.isArray(state.hotelRefs) || state.hotelRefs.length > MAX_HOTELS_PER_TASK)
541
+ return false;
542
+ if (state.hotelRefs.some((ref) => typeof ref !== 'string' || !ref) || new Set(state.hotelRefs).size !== state.hotelRefs.length)
543
+ return false;
544
+ if ((state.hotelRefs.length && (state.activeHotelOrdinal < 0 || state.activeHotelOrdinal >= state.hotelRefs.length)) || (!state.hotelRefs.length && state.activeHotelOrdinal !== 0))
545
+ return false;
546
+ if (!state.hotels || typeof state.hotels !== 'object' || Object.keys(state.hotels).sort().join('\0') !== [...state.hotelRefs].sort().join('\0'))
547
+ return false;
548
+ if (!Array.isArray(state.attempts) || !Array.isArray(state.queryReservations))
549
+ return false;
550
+ if (!state.recoveryStarted) {
551
+ if (state.recoveryId !== undefined || state.candidateSetDigest !== undefined || state.hotelRefs.length || Object.keys(state.hotels).length || state.attempts.length || state.queryReservations.length || state.terminal !== undefined)
552
+ return false;
553
+ if (state.criteria !== undefined || state.criteriaDigest !== undefined) {
554
+ if (!isOfferCriteria(state.criteria) || !state.criteriaDigest || state.criteriaDigest !== digest(state.criteria))
555
+ return false;
556
+ }
557
+ if (state.lastQueryCriteriaDigest !== undefined && state.lastQueryCriteriaDigest !== state.criteriaDigest)
558
+ return false;
559
+ if (state.lastQueryHotelRefs !== undefined && (!Array.isArray(state.lastQueryHotelRefs) || new Set(state.lastQueryHotelRefs).size !== state.lastQueryHotelRefs.length || state.lastQueryHotelRefs.some((ref) => typeof ref !== 'string' || !ref)))
560
+ return false;
561
+ if (state.lastQueryOfferHotels !== undefined && (!state.lastQueryHotelRefs || Object.values(state.lastQueryOfferHotels).some((ref) => typeof ref !== 'string' || !state.lastQueryHotelRefs.includes(ref)) || Object.keys(state.lastQueryOfferHotels).some((ref) => typeof ref !== 'string' || !ref)))
562
+ return false;
563
+ if (state.lastQuerySourceActionId !== undefined && (typeof state.lastQuerySourceActionId !== 'string' || !state.lastQuerySourceActionId || typeof state.lastQuerySourceReceiptDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.lastQuerySourceReceiptDigest) || !Number.isSafeInteger(state.lastQueryWorkspaceRevision) || state.lastQueryWorkspaceRevision < 0))
564
+ return false;
565
+ if (state.lastQuerySourceReceiptDigest !== undefined && state.lastQuerySourceActionId === undefined)
566
+ return false;
567
+ return state.initialized === false && state.availabilityPhase === 'need_offers';
568
+ }
569
+ if (!state.initialized || !state.recoveryId || !state.candidateSetDigest || state.candidateSetDigest !== digest(state.hotelRefs))
570
+ return false;
571
+ const criteriaBound = isOfferCriteria(state.criteria) && Boolean(state.criteriaDigest) && state.criteriaDigest === digest(state.criteria);
572
+ const manualCheckWorkspaceSnapshot = state.attempts.length > 0 && state.attempts.every((attempt) => state.hotels[attempt.hotelRef]?.currentGeneration?.source.kind === 'workspace_snapshot');
573
+ const manualCheckMayBindCriteria = !state.criteria && !state.criteriaDigest && state.queryReservations.length === 0 && manualCheckWorkspaceSnapshot && (['waiting_check', 'need_offers'].includes(state.availabilityPhase)
574
+ || state.availabilityPhase === 'need_check'
575
+ || (state.availabilityPhase === 'terminal' && state.terminal?.code === 'availability_confirmed'));
576
+ if (!criteriaBound && !manualCheckMayBindCriteria)
577
+ return false;
578
+ if (state.lastQueryCriteriaDigest !== undefined && state.lastQueryCriteriaDigest !== state.criteriaDigest)
579
+ return false;
580
+ if (state.lastQueryHotelRefs !== undefined && (!Array.isArray(state.lastQueryHotelRefs) || new Set(state.lastQueryHotelRefs).size !== state.lastQueryHotelRefs.length || state.lastQueryHotelRefs.some((ref) => typeof ref !== 'string' || !ref)))
581
+ return false;
582
+ if (state.lastQueryOfferHotels !== undefined && (!state.lastQueryHotelRefs || Object.values(state.lastQueryOfferHotels).some((ref) => typeof ref !== 'string' || !state.lastQueryHotelRefs.includes(ref)) || Object.keys(state.lastQueryOfferHotels).some((ref) => typeof ref !== 'string' || !ref)))
583
+ return false;
584
+ if (state.lastQuerySourceActionId !== undefined && (typeof state.lastQuerySourceActionId !== 'string' || !state.lastQuerySourceActionId || typeof state.lastQuerySourceReceiptDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.lastQuerySourceReceiptDigest) || !Number.isSafeInteger(state.lastQueryWorkspaceRevision) || state.lastQueryWorkspaceRevision < 0))
585
+ return false;
586
+ if (state.lastQuerySourceReceiptDigest !== undefined && state.lastQuerySourceActionId === undefined)
587
+ return false;
588
+ if (state.hotelRefs.length === 0)
589
+ return false;
590
+ const validStatus = new Set(['unvisited', 'active', 'negative', 'confirmed', 'inconclusive']);
591
+ const validEvidence = new Set(['none', 'confirmed', 'unavailable', 'inconclusive']);
592
+ for (const ref of state.hotelRefs) {
593
+ const hotel = state.hotels[ref];
594
+ if (!hotel || hotel.hotelRef !== ref || !validStatus.has(hotel.status) || !validEvidence.has(hotel.lastEvidence))
595
+ return false;
596
+ if (![hotel.checksIssued, hotel.offerQueriesIssued, hotel.generationNo, hotel.generation, hotel.checkCount].every(Number.isSafeInteger))
597
+ return false;
598
+ if (hotel.checksIssued < 0 || hotel.checksIssued > MAX_OFFER_CHECKS_PER_HOTEL || hotel.offerQueriesIssued < 0 || hotel.offerQueriesIssued > MAX_OFFER_QUERIES_PER_HOTEL || hotel.generationNo < 0 || hotel.generation !== hotel.generationNo || hotel.checkCount !== hotel.checksIssued)
599
+ return false;
600
+ if (typeof hotel.freshOffersRequired !== 'boolean' || !Array.isArray(hotel.currentOfferRefs) || !Array.isArray(hotel.invalidatedOfferRefs) || !Array.isArray(hotel.tombstonedOfferRefs) || !Array.isArray(hotel.tombstonedOfferVersionRefs))
601
+ return false;
602
+ if (hotel.currentOfferRefs.some((offer) => typeof offer !== 'string' || !offer) || hotel.invalidatedOfferRefs.some((offer) => typeof offer !== 'string' || !offer) || hotel.tombstonedOfferRefs.some((offer) => typeof offer !== 'string' || !offer) || hotel.tombstonedOfferVersionRefs.some((offer) => typeof offer !== 'string' || !offer))
603
+ return false;
604
+ if (new Set(hotel.currentOfferRefs).size !== hotel.currentOfferRefs.length || new Set(hotel.invalidatedOfferRefs).size !== hotel.invalidatedOfferRefs.length || new Set(hotel.tombstonedOfferRefs).size !== hotel.tombstonedOfferRefs.length || new Set(hotel.tombstonedOfferVersionRefs).size !== hotel.tombstonedOfferVersionRefs.length || hotel.currentOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || hotel.invalidatedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || hotel.currentOfferRefs.some((offer) => hotel.invalidatedOfferRefs.includes(offer) || hotel.tombstonedOfferRefs.includes(offer)))
605
+ return false;
606
+ const generation = hotel.currentGeneration;
607
+ if (hotel.generationNo === 0) {
608
+ if (generation !== undefined || hotel.currentOfferRefs.length || hotel.invalidatedOfferRefs.length || hotel.tombstonedOfferRefs.length || hotel.tombstonedOfferVersionRefs.length || hotel.status !== 'unvisited' || hotel.lastEvidence !== 'none' || hotel.checksIssued !== 0 || hotel.offerQueriesIssued !== 0 || hotel.freshOffersRequired)
609
+ return false;
610
+ continue;
611
+ }
612
+ if (!generation || generation.generationId !== `${ref}:generation:${hotel.generationNo}` || !/^[0-9a-f]{64}$/.test(generation.offerSetDigest) || generation.offerSetDigest !== digest(generation.orderedOfferRefs) || !Array.isArray(generation.orderedOfferRefs) || generation.orderedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || generation.orderedOfferRefs.some((offer) => typeof offer !== 'string' || !offer) || new Set(generation.orderedOfferRefs).size !== generation.orderedOfferRefs.length || !['complete', 'partial'].includes(generation.evidence) || typeof generation.valid !== 'boolean')
613
+ return false;
614
+ if (generation.offerSetDigest !== digest(generation.orderedOfferRefs) || hotel.currentOfferRefs.some((offer) => !generation.orderedOfferRefs.includes(offer)) || hotel.invalidatedOfferRefs.some((offer) => !generation.orderedOfferRefs.includes(offer)))
615
+ return false;
616
+ const source = generation.source;
617
+ if (!source || !['query_receipt', 'workspace_snapshot'].includes(source.kind) || !Number.isSafeInteger(source.workspaceRevision) || source.workspaceRevision < 0)
618
+ return false;
619
+ if (source.kind === 'query_receipt' && (typeof source.actionId !== 'string' || !source.actionId || !/^[0-9a-f]{64}$/.test(source.receiptDigest)))
620
+ return false;
621
+ if (source.kind === 'workspace_snapshot' && (typeof source.workspaceDigest !== 'string' || !/^[0-9a-f]{64}$/.test(source.workspaceDigest)))
622
+ return false;
623
+ if (source.kind === 'query_receipt' && state.lastQuerySourceActionId === source.actionId && (state.lastQuerySourceReceiptDigest !== source.receiptDigest || state.lastQueryWorkspaceRevision !== source.workspaceRevision))
624
+ return false;
625
+ if (hotel.freshOffersRequired && (hotel.currentOfferRefs.length !== 0 || hotel.checksIssued >= MAX_OFFER_CHECKS_PER_HOTEL || hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL))
626
+ return false;
627
+ if (hotel.status === 'unvisited' || hotel.status === 'negative') {
628
+ if (hotel.status === 'unvisited' || hotel.lastEvidence !== 'unavailable' || hotel.currentOfferRefs.length || generation.valid)
629
+ return false;
630
+ }
631
+ if (hotel.status === 'active' && (!hotel.currentOfferRefs.length || hotel.lastEvidence !== 'none' || hotel.freshOffersRequired))
632
+ return false;
633
+ if (hotel.status === 'confirmed' && (hotel.lastEvidence !== 'confirmed' || !hotel.currentOfferRefs.length || generation.valid))
634
+ return false;
635
+ if (hotel.status === 'inconclusive' && (hotel.lastEvidence !== 'inconclusive' || hotel.currentOfferRefs.length || generation.valid))
636
+ return false;
637
+ }
638
+ const attempts = state.attempts;
639
+ if (new Set(attempts.map((attempt) => attempt.actionId)).size !== attempts.length)
640
+ return false;
641
+ const attemptsByHotel = new Map();
642
+ for (const attempt of attempts) {
643
+ if (typeof attempt.actionId !== 'string' || !attempt.actionId || !state.hotels[attempt.hotelRef] || typeof attempt.offerRef !== 'string' || !attempt.offerRef || typeof attempt.offerVersionRef !== 'string' || !attempt.offerVersionRef || !Number.isSafeInteger(attempt.generation) || !Number.isSafeInteger(attempt.ordinal) || !Number.isSafeInteger(attempt.workspaceRevision) || attempt.workspaceRevision < 0)
644
+ return false;
645
+ const hotel = state.hotels[attempt.hotelRef];
646
+ const generation = hotel.currentGeneration;
647
+ if (!generation || attempt.generation < 1 || attempt.generation > hotel.generationNo || attempt.ordinal < 1 || attempt.ordinal > hotel.checksIssued || (attempt.generation === hotel.generationNo && !generation.orderedOfferRefs.includes(attempt.offerRef)))
648
+ return false;
649
+ const list = attemptsByHotel.get(attempt.hotelRef) ?? [];
650
+ list.push(attempt);
651
+ attemptsByHotel.set(attempt.hotelRef, list);
652
+ }
653
+ for (const [ref, attemptsForHotel] of attemptsByHotel) {
654
+ const ordinals = attemptsForHotel.map((attempt) => attempt.ordinal);
655
+ if (new Set(ordinals).size !== ordinals.length || attemptsForHotel.length !== state.hotels[ref].checksIssued || new Set(attemptsForHotel.map((attempt) => `${attempt.generation}:${attempt.offerRef}:${attempt.offerVersionRef}`)).size !== attemptsForHotel.length)
656
+ return false;
657
+ }
658
+ for (const ref of state.hotelRefs)
659
+ if ((attemptsByHotel.get(ref)?.length ?? 0) !== state.hotels[ref].checksIssued)
660
+ return false;
661
+ if (new Set(state.queryReservations.map((reservation) => reservation.actionId)).size !== state.queryReservations.length)
662
+ return false;
663
+ const reservedHotels = new Set();
664
+ for (const reservation of state.queryReservations) {
665
+ if (typeof reservation.actionId !== 'string' || !reservation.actionId || !Array.isArray(reservation.hotelRefs) || !reservation.hotelRefs.length || reservation.hotelRefs.length > MAX_HOTELS_PER_TASK || new Set(reservation.hotelRefs).size !== reservation.hotelRefs.length || reservation.hotelRefs.some((ref) => !state.hotels[ref]) || !Number.isSafeInteger(reservation.workspaceRevision) || reservation.workspaceRevision < 0 || reservation.hotelRefs.some((ref) => reservedHotels.has(ref)))
666
+ return false;
667
+ reservation.hotelRefs.forEach((ref) => reservedHotels.add(ref));
668
+ }
669
+ if (state.availabilityPhase === 'waiting_offers' && state.queryReservations.length === 0)
670
+ return false;
671
+ if (state.availabilityPhase !== 'waiting_offers' && state.queryReservations.length > 0)
672
+ return false;
673
+ if (state.availabilityPhase === 'waiting_check' && !attempts.length)
674
+ return false;
675
+ if (state.availabilityPhase === 'need_check') {
676
+ const active = state.hotels[state.hotelRefs[state.activeHotelOrdinal]];
677
+ if (active.status !== 'active' || !active.currentGeneration?.valid || !active.currentOfferRefs.length || active.freshOffersRequired)
678
+ return false;
679
+ }
680
+ if (state.availabilityPhase === 'terminal' && !state.terminal)
681
+ return false;
682
+ if (state.availabilityPhase !== 'terminal' && state.terminal)
683
+ return false;
684
+ const recomputedTerminal = terminalFor(state);
685
+ if (state.terminal) {
686
+ if (!recomputedTerminal || digest(state.terminal) !== digest(recomputedTerminal))
687
+ return false;
688
+ }
689
+ else if (recomputedTerminal && !['waiting_offers', 'waiting_check'].includes(state.availabilityPhase))
690
+ return false;
691
+ return true;
692
+ }
693
+ catch {
694
+ return false;
695
+ }
696
+ }
697
+ export function availabilityPolicyActionIsRelevant(action) { return action.kind === 'offers.query' || action.kind === 'offer.check'; }