@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,126 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { ensureLedger } from '../src/state-ledger.js';
6
+ import { bookingDigest } from '../src/booking-surface/runtime.js';
7
+ import { BookingCopilotProofRuntime as BookingCopilotTaskRuntime } from './booking-copilot-proof-runtime.js';
8
+ const offerVerifiedIntent = { schemaVersion: 'booking.intent.v1', target: 'offer.verified' };
9
+ const workspace = (revision) => ({
10
+ schemaVersion: 'booking.surface', contextRef: 'ctx-ledger-binding', surface: 'tenant', revision,
11
+ locale: 'en-US', currency: 'AED', searchDraft: {}, results: { status: 'idle' },
12
+ visibleHotels: [{ hotelRef: 'h1', name: 'H1', factRefs: [] }, { hotelRef: 'h2', name: 'H2', factRefs: [] }],
13
+ loadedOffers: [{ offerRef: 'o1', offerVersionRef: 'o1:v1', hotelRef: 'h1', evidenceLevel: 'rate_loaded', factRefs: [] }, { offerRef: 'o2', offerVersionRef: 'o2:v1', hotelRef: 'h2', evidenceLevel: 'rate_loaded', factRefs: [] }],
14
+ shortlistedOfferRefs: ['o1', 'o2'], capabilities: { surface: 'tenant', allowedActions: ['offers.query', 'offer.check'] },
15
+ });
16
+ const offersReceipt = (actionId, revision) => ({
17
+ schemaVersion: 'booking.surface', kind: 'action.receipt', actionId, contextRef: 'ctx-ledger-binding', status: 'applied', revision,
18
+ observation: { kind: 'offers.state', hotelRefs: ['h1'], offerRefs: ['o1'], loadedHotelCount: 1 },
19
+ resultContract: { outcome: 'complete', hardCriteriaMet: true, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] },
20
+ });
21
+ const checkReceipt = (actionId, revision) => ({
22
+ schemaVersion: 'booking.surface', kind: 'action.receipt', actionId, contextRef: 'ctx-ledger-binding', status: 'unavailable', revision,
23
+ observation: { kind: 'offer.availability', offerRef: 'o1', checkedOfferVersionRef: 'o1:v1', available: false, changedFactRefs: [], gapCodes: [] },
24
+ resultContract: { outcome: 'empty', hardCriteriaMet: false, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] },
25
+ });
26
+ function fixture(stage) {
27
+ const root = mkdtempSync(join(tmpdir(), 'gotry-availability-ledger-binding-'));
28
+ const ledger = ensureLedger(root);
29
+ const runtime = new BookingCopilotTaskRuntime(ledger, { contextRefFactory: () => 'ctx-ledger-binding' });
30
+ const task = 'ledger-binding-task';
31
+ runtime.startTask({ schemaVersion: 'booking.surface', kind: 'user.turn', taskId: task, turnId: 'turn-1', workspace: workspace(0), request: { text: 'find rates' } });
32
+ const query = { schemaVersion: 'booking.surface', kind: 'offers.query', actionId: 'query-1', contextRef: 'ctx-ledger-binding', expectedRevision: 0, reason: 'load offers', factRefs: [], input: { hotelRefs: ['h1'], criteria: {} } };
33
+ runtime.issueOperation(task, query, offerVerifiedIntent);
34
+ runtime.continueWithReceipt({ schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: task, workspace: workspace(1), receipt: offersReceipt('query-1', 1) });
35
+ const check = { schemaVersion: 'booking.surface', kind: 'offer.check', actionId: 'check-1', contextRef: 'ctx-ledger-binding', expectedRevision: 1, reason: 'check', factRefs: [], input: { offerRef: 'o1', offerVersionRef: 'o1:v1' } };
36
+ runtime.issueOperation(task, check, offerVerifiedIntent);
37
+ if (stage === 'receipt')
38
+ runtime.continueWithReceipt({ schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: task, workspace: { ...workspace(2), shortlistedOfferRefs: ['o2'], selectedOfferRef: undefined, verifiedOffer: undefined }, receipt: checkReceipt('check-1', 2) });
39
+ ledger.close();
40
+ return { root, ledger, task };
41
+ }
42
+ function rewrite(root, kind, mutate) {
43
+ const ledger = ensureLedger(root);
44
+ const row = ledger.db.prepare('SELECT seq, payload FROM events WHERE run_id = ? AND kind = ? ORDER BY seq DESC LIMIT 1').get('ledger-binding-task', kind);
45
+ assert.ok(row);
46
+ const payload = JSON.parse(row.payload);
47
+ mutate(payload);
48
+ ledger.db.prepare('UPDATE events SET payload = ? WHERE seq = ?').run(JSON.stringify(payload), row.seq);
49
+ ledger.close();
50
+ }
51
+ function assertCorrupt(root, label) {
52
+ const ledger = ensureLedger(root);
53
+ const runtime = new BookingCopilotTaskRuntime(ledger, { contextRefFactory: () => 'ctx-ledger-binding' });
54
+ assert.throws(() => runtime.resumeTask('ledger-binding-task'), /ledger_corrupt:ledger-binding-task/, label);
55
+ ledger.close();
56
+ }
57
+ // The untouched durable sequence is replayable after reopening the ledger.
58
+ {
59
+ const fixtureState = fixture('action');
60
+ const ledger = ensureLedger(fixtureState.root);
61
+ assert.doesNotThrow(() => new BookingCopilotTaskRuntime(ledger).resumeTask(fixtureState.task));
62
+ ledger.close();
63
+ rmSync(fixtureState.root, { recursive: true, force: true });
64
+ }
65
+ for (const [label, mutate] of [
66
+ ['candidate digest', (payload) => { payload.availability.candidateSetDigest = 'f'.repeat(64); payload.availabilityDigest = bookingDigest(payload.availability); }],
67
+ ['offer set digest', (payload) => { payload.availability.hotels.h1.currentGeneration.offerSetDigest = 'e'.repeat(64); payload.availabilityDigest = bookingDigest(payload.availability); }],
68
+ ['source receipt digest', (payload) => { payload.availability.hotels.h1.currentGeneration.source.receiptDigest = 'd'.repeat(64); payload.availabilityDigest = bookingDigest(payload.availability); }],
69
+ ['active ordinal', (payload) => { payload.availability.activeHotelOrdinal = 1; payload.availabilityDigest = bookingDigest(payload.availability); }],
70
+ ['fake terminal', (payload) => { payload.availability.terminal = { code: 'availability_confirmed', hotelRefs: ['h1', 'h2'], reason: 'confirmed', evidence: 'conclusive' }; payload.availability.availabilityPhase = 'terminal'; payload.availabilityDigest = bookingDigest(payload.availability); }],
71
+ ]) {
72
+ const fixtureState = fixture('action');
73
+ rewrite(fixtureState.root, 'booking.copilot.action.issued', mutate);
74
+ assertCorrupt(fixtureState.root, label);
75
+ rmSync(fixtureState.root, { recursive: true, force: true });
76
+ }
77
+ // A digest mismatch must be rejected before any legacy/normalized availability
78
+ // migration can make the payload appear coherent.
79
+ {
80
+ const fixtureState = fixture('action');
81
+ rewrite(fixtureState.root, 'booking.copilot.action.issued', (payload) => { payload.availability.activeHotelOrdinal = 1; });
82
+ assertCorrupt(fixtureState.root, 'raw ACTION availability digest mismatch');
83
+ rmSync(fixtureState.root, { recursive: true, force: true });
84
+ }
85
+ {
86
+ const fixtureState = fixture('receipt');
87
+ rewrite(fixtureState.root, 'booking.copilot.receipt.observed', (payload) => { payload.availability.activeHotelOrdinal = 1; });
88
+ assertCorrupt(fixtureState.root, 'raw RECEIPT availability digest mismatch');
89
+ rmSync(fixtureState.root, { recursive: true, force: true });
90
+ }
91
+ // A forged action can be structurally valid and re-digested, but cannot change
92
+ // the transition that the prior state and workspace authorize.
93
+ {
94
+ const fixtureState = fixture('action');
95
+ rewrite(fixtureState.root, 'booking.copilot.action.issued', (payload) => {
96
+ payload.action.input.offerRef = 'o2';
97
+ const { actionDigest: _ignored, ...base } = payload.action;
98
+ payload.action.actionDigest = bookingDigest(base);
99
+ });
100
+ assertCorrupt(fixtureState.root, 'wrong action transition');
101
+ rmSync(fixtureState.root, { recursive: true, force: true });
102
+ }
103
+ // The receipt shape and canonical receipt digest can both be valid while the
104
+ // availability transition is forged; replay must still derive the mismatch.
105
+ {
106
+ const fixtureState = fixture('receipt');
107
+ const runtimeLedger = ensureLedger(fixtureState.root);
108
+ const runtime = new BookingCopilotTaskRuntime(runtimeLedger);
109
+ assert.deepEqual(runtime.resumeTask(fixtureState.task)?.availability.criteria, {}, 'recovery retains the canonical offer criteria after restart');
110
+ const wrongDetour = { schemaVersion: 'booking.surface', kind: 'search.run', actionId: 'detour', contextRef: 'ctx-ledger-binding', expectedRevision: 2, reason: 'detour', factRefs: [], input: {} };
111
+ const activeIntent = runtime.resumeTask(fixtureState.task)?.activeIntent?.projection;
112
+ assert.ok(activeIntent);
113
+ assert.throws(() => runtime.applyDecisionBatch(fixtureState.task, 'detour-batch', [{ kind: 'operation', action: wrongDetour, intent: activeIntent }]), /availability_operation_incompatible/);
114
+ runtimeLedger.close();
115
+ rmSync(fixtureState.root, { recursive: true, force: true });
116
+ }
117
+ {
118
+ const fixtureState = fixture('receipt');
119
+ rewrite(fixtureState.root, 'booking.copilot.receipt.observed', (payload) => {
120
+ payload.receipt.status = 'applied';
121
+ payload.receiptDigest = bookingDigest(payload.receipt);
122
+ });
123
+ assertCorrupt(fixtureState.root, 'wrong receipt transition');
124
+ rmSync(fixtureState.root, { recursive: true, force: true });
125
+ }
126
+ console.log('BOOKING COPILOT AVAILABILITY LEDGER BINDING: exact replay and candidate/generation/source/ordinal/terminal/action/receipt tamper rejection OK');
@@ -0,0 +1,265 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { ensureLedger } from '../src/state-ledger.js';
6
+ import { BookingCopilotProofRuntime as BookingCopilotTaskRuntime } from './booking-copilot-proof-runtime.js';
7
+ import { createAvailabilityPolicy, digest, MAX_HOTELS_PER_TASK, MAX_OFFER_CHECKS_PER_HOTEL, MAX_OFFER_QUERIES_PER_HOTEL, recordObservedOffersQuery, recordOfferCheckIssued, recordOfferCheckReceipt, recordOffersGeneration, recordOffersQueryIssued, canIssueOfferCheck } from '../src/booking-surface/availability-policy.js';
8
+ import { bookingSurfaceAllowedActions, bookingSurfaceFleetFeatures, bookingSurfaceNegotiatedActions } from '../src/booking-surface/contracts.js';
9
+ const versionFor = (offerRef, suffix = 'v1') => `${offerRef}:${suffix}`;
10
+ const verifiedCapability = (offerRef, offerVersionRef = versionFor(offerRef), expiresAt = '2026-09-01T10:30:00.000Z') => ({ offerRef, offerVersionRef, verifiedOfferRef: `verified-${offerRef}`, expiresAt });
11
+ const ws = (revision, hotels, offers, shortlistedOfferRefs = [], verifiedOffer) => ({ schemaVersion: 'booking.surface', contextRef: 'ctx-availability', surface: 'tenant', revision, locale: 'en-US', currency: 'AED', searchDraft: {}, results: { status: 'idle' }, visibleHotels: hotels.map(h => ({ hotelRef: h, name: h, factRefs: [] })), loadedOffers: offers.map(([offerRef, hotelRef, offerVersionRef = versionFor(offerRef)]) => ({ offerRef, offerVersionRef, hotelRef, evidenceLevel: 'rate_loaded', factRefs: [] })), shortlistedOfferRefs, capabilities: { surface: 'tenant', allowedActions: ['offers.query', 'offer.check'] }, ...(verifiedOffer ? { verifiedOffer } : {}) });
12
+ const receipt = (actionId, revision, offerRef, status = 'unavailable', available = false, checkedOfferVersionRef = versionFor(offerRef), currentOfferVersionRef) => ({ schemaVersion: 'booking.surface', kind: 'action.receipt', actionId, contextRef: 'ctx-availability', status, revision, observation: { kind: 'offer.availability', offerRef, checkedOfferVersionRef, currentOfferVersionRef: available ? (currentOfferVersionRef ?? checkedOfferVersionRef) : undefined, available, verifiedOfferRef: available ? 'verified-' + offerRef : undefined, changedFactRefs: [], gapCodes: [] }, resultContract: { outcome: available ? 'complete' : 'empty', hardCriteriaMet: available, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] } });
13
+ const foldQuery = (state, hotelRefs, workspace, actionId, queryReceipt, criteriaDigest = '') => recordOffersGeneration(state, hotelRefs, workspace, actionId, digest(queryReceipt), queryReceipt, criteriaDigest);
14
+ const criteria = {};
15
+ const offerVerifiedIntent = { schemaVersion: 'booking.intent.v1', target: 'offer.verified' };
16
+ const offersReceipt = (actionId, revision, hotelRefs, offerRefs, loadedHotelCount, status = 'applied', outcome = 'complete', hardCriteriaMet = true) => ({ schemaVersion: 'booking.surface', kind: 'action.receipt', actionId, contextRef: 'ctx-availability', status, revision, observation: { kind: 'offers.state', hotelRefs, offerRefs, loadedHotelCount, gapCodes: [] }, resultContract: { outcome, hardCriteriaMet, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] } });
17
+ const gapReceipt = (actionId, revision, status = 'failed') => ({ schemaVersion: 'booking.surface', kind: 'action.receipt', actionId, contextRef: 'ctx-availability', status, revision, observation: { kind: 'gap', code: 'hotel_rates_failed', factRefs: [] }, resultContract: { outcome: 'partial', hardCriteriaMet: false, factRefs: [], gapCodes: ['hotel_rates_failed'], blockers: [], relaxationsApplied: [] } });
18
+ // UAT-facing product availability observation: these are exact least-privilege
19
+ // sets, and are intentionally separate from the full tenant action vocabulary.
20
+ assert.deepEqual(bookingSurfaceAllowedActions('storefront'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus']);
21
+ assert.deepEqual(bookingSurfaceAllowedActions('payment_link'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus', 'hotel.select']);
22
+ assert.deepEqual(bookingSurfaceAllowedActions('tenant'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus', 'offers.query', 'offers.view.patch', 'offers.compare', 'offer.select', 'offer.check', 'checkout.prepare', 'order.observe']);
23
+ assert.deepEqual(bookingSurfaceAllowedActions('customer_portal'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus', 'offers.query', 'offers.view.patch', 'offers.compare', 'offer.select', 'offer.check', 'checkout.prepare', 'order.observe']);
24
+ assert.equal(bookingSurfaceAllowedActions('tenant').includes('hotel.select'), false);
25
+ assert.equal(bookingSurfaceAllowedActions('customer_portal').includes('hotel.select'), false);
26
+ // Additive compatibility is negotiated from the fleet intersection, never
27
+ // from a schema hash or a single lucky load-balanced probe.
28
+ assert.deepEqual(bookingSurfaceFleetFeatures([{ features: ['trusted-order-observation-v1'] }]), ['trusted-order-observation-v1']);
29
+ assert.deepEqual(bookingSurfaceFleetFeatures([{ features: ['trusted-order-observation-v1'] }, {}]), [], 'an old peer with no feature advertisement disables the additive field');
30
+ assert.deepEqual(bookingSurfaceFleetFeatures([{ features: ['trusted-order-observation-v1'] }, { features: [] }]), [], 'a mixed rolling fleet uses the safe intersection');
31
+ assert.deepEqual(bookingSurfaceFleetFeatures([{ features: ['trusted-order-observation-v1', 'future-feature'] }]), ['trusted-order-observation-v1'], 'unknown future features do not poison known negotiation');
32
+ assert.equal(bookingSurfaceNegotiatedActions('tenant', [{ schemaSha256: 'same-hash-is-not-authority' }]).includes('order.observe'), false, 'schema hash never enables an additive action');
33
+ assert.equal(bookingSurfaceNegotiatedActions('tenant', [{ features: ['trusted-order-observation-v1'] }]).includes('order.observe'), true, 'the advertised fleet feature enables order observation');
34
+ assert.deepEqual(bookingSurfaceNegotiatedActions('storefront', [{ features: ['trusted-order-observation-v1'] }]), bookingSurfaceAllowedActions('storefront'), 'feature negotiation cannot expand the product surface matrix');
35
+ // Lifetime budget mutation guard: these assertions exercise the transitions,
36
+ // not just the exported constants, so changing a comparison or reset policy
37
+ // makes this proof fail.
38
+ {
39
+ assert.equal(MAX_HOTELS_PER_TASK, 5);
40
+ assert.equal(MAX_OFFER_CHECKS_PER_HOTEL, 2);
41
+ assert.equal(MAX_OFFER_QUERIES_PER_HOTEL, 2);
42
+ const unavailableWorkspace = ws(0, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]);
43
+ let exhausted = recordOfferCheckIssued(createAvailabilityPolicy(unavailableWorkspace), unavailableWorkspace, 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 'budget-check-1');
44
+ exhausted = recordOfferCheckReceipt(exhausted, ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), receipt('budget-check-1', 1, 'budget-hotel:ratepkg-1'), 'budget-check-1', 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 0);
45
+ assert.equal(exhausted.hotels['budget-hotel']?.freshOffersRequired, true, 'first unavailable opens exactly one fresh-generation transition');
46
+ exhausted = recordOffersQueryIssued(exhausted, ['budget-hotel'], ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), 'budget-query-1');
47
+ const reusedReceipt = offersReceipt('budget-query-1', 2, ['budget-hotel'], ['budget-hotel:ratepkg-1'], 1);
48
+ const reused = recordOffersGeneration(exhausted, ['budget-hotel'], ws(2, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), 'budget-query-1', digest(reusedReceipt), reusedReceipt);
49
+ assert.deepEqual(reused.hotels['budget-hotel']?.currentOfferRefs, [], 'fresh HotelRates cannot re-admit the checked stale OfferRef');
50
+ assert.equal(reused.hotels['budget-hotel']?.freshOffersRequired, true, 'stale OfferRef reuse leaves an explicit refresh transition');
51
+ const refreshedReceipt = offersReceipt('budget-query-1', 2, ['budget-hotel'], ['budget-hotel:ratepkg-2'], 1);
52
+ exhausted = recordOffersGeneration(exhausted, ['budget-hotel'], ws(2, ['budget-hotel'], [['budget-hotel:ratepkg-2', 'budget-hotel']]), 'budget-query-1', digest(refreshedReceipt), refreshedReceipt);
53
+ exhausted = recordOfferCheckIssued(exhausted, ws(2, ['budget-hotel'], [['budget-hotel:ratepkg-2', 'budget-hotel']]), 'budget-hotel:ratepkg-2', versionFor('budget-hotel:ratepkg-2'), 'budget-check-2');
54
+ exhausted = recordOfferCheckReceipt(exhausted, ws(3, ['budget-hotel'], [['budget-hotel:ratepkg-2', 'budget-hotel']]), receipt('budget-check-2', 3, 'budget-hotel:ratepkg-2'), 'budget-check-2', 'budget-hotel:ratepkg-2', versionFor('budget-hotel:ratepkg-2'), 2);
55
+ assert.equal(exhausted.hotels['budget-hotel']?.checksIssued, 2, 'second unavailable consumes the lifetime CheckAvail budget');
56
+ assert.equal(exhausted.terminal?.code, 'availability_exhausted_complete', 'second unavailable reaches conclusive exhaustion');
57
+ assert.throws(() => recordOfferCheckIssued(exhausted, ws(3, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 'budget-check-3'), /availability_terminal/, 'third CheckAvail cannot be issued after terminal exhaustion');
58
+ const unavailableWithGap = (actionId, revision) => ({
59
+ ...receipt(actionId, revision, 'budget-hotel:ratepkg-1'),
60
+ observation: { kind: 'offer.availability', offerRef: 'budget-hotel:ratepkg-1', checkedOfferVersionRef: versionFor('budget-hotel:ratepkg-1'), available: false, changedFactRefs: [], gapCodes: ['hotel_rates_failed'] },
61
+ resultContract: { outcome: 'empty', hardCriteriaMet: false, factRefs: [], gapCodes: ['hotel_rates_failed'], blockers: [], relaxationsApplied: [] },
62
+ });
63
+ let inconclusive = recordOfferCheckIssued(createAvailabilityPolicy(unavailableWorkspace), unavailableWorkspace, 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 'gap-check-1');
64
+ inconclusive = recordOfferCheckReceipt(inconclusive, ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), unavailableWithGap('gap-check-1', 1), 'gap-check-1', 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 0);
65
+ assert.equal(inconclusive.hotels['budget-hotel']?.status, 'inconclusive', 'generic gap is not accepted as a typed negative');
66
+ assert.equal(inconclusive.hotels['budget-hotel']?.tombstonedOfferRefs.includes('budget-hotel:ratepkg-1'), false, 'generic gap cannot tombstone logical offer');
67
+ assert.equal(inconclusive.hotels['budget-hotel']?.tombstonedOfferVersionRefs.includes(versionFor('budget-hotel:ratepkg-1')), false, 'generic gap cannot tombstone offer version');
68
+ inconclusive = recordOffersQueryIssued(inconclusive, ['budget-hotel'], ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), 'gap-query-1');
69
+ const gapRefreshedReceipt = offersReceipt('gap-query-1', 2, ['budget-hotel'], ['budget-hotel:ratepkg-3'], 1);
70
+ inconclusive = recordOffersGeneration(inconclusive, ['budget-hotel'], ws(2, ['budget-hotel'], [['budget-hotel:ratepkg-3', 'budget-hotel']]), 'gap-query-1', digest(gapRefreshedReceipt), gapRefreshedReceipt);
71
+ inconclusive = recordOfferCheckIssued(inconclusive, ws(2, ['budget-hotel'], [['budget-hotel:ratepkg-3', 'budget-hotel']]), 'budget-hotel:ratepkg-3', versionFor('budget-hotel:ratepkg-3'), 'gap-check-2');
72
+ inconclusive = recordOfferCheckReceipt(inconclusive, ws(3, ['budget-hotel'], [['budget-hotel:ratepkg-3', 'budget-hotel']]), { ...unavailableWithGap('gap-check-2', 3), observation: { kind: 'offer.availability', offerRef: 'budget-hotel:ratepkg-3', checkedOfferVersionRef: versionFor('budget-hotel:ratepkg-3'), available: false, changedFactRefs: [], gapCodes: [] } }, 'gap-check-2', 'budget-hotel:ratepkg-3', versionFor('budget-hotel:ratepkg-3'), 2);
73
+ assert.equal(inconclusive.terminal?.code, 'availability_exhausted_inconclusive', 'an unavailable receipt with a gap never masquerades as conclusive exhaustion');
74
+ for (const status of ['unavailable', 'no_match']) {
75
+ const gapAction = `generic-gap-${status}`;
76
+ const gapState = recordOfferCheckIssued(createAvailabilityPolicy(unavailableWorkspace), unavailableWorkspace, 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), gapAction);
77
+ const gap = { ...receipt(gapAction, 1, 'budget-hotel:ratepkg-1', status), observation: { kind: 'offer.availability', offerRef: 'budget-hotel:ratepkg-1', checkedOfferVersionRef: versionFor('budget-hotel:ratepkg-1'), available: false, changedFactRefs: [], gapCodes: ['hotel_rates_failed'] }, resultContract: { outcome: 'empty', hardCriteriaMet: false, factRefs: [], gapCodes: ['hotel_rates_failed'], blockers: [], relaxationsApplied: [] } };
78
+ const afterGap = recordOfferCheckReceipt(gapState, ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), gap, gapAction, 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 0);
79
+ assert.equal(afterGap.hotels['budget-hotel']?.status, 'inconclusive', `${status} with generic gap remains inconclusive`);
80
+ assert.equal(afterGap.hotels['budget-hotel']?.tombstonedOfferRefs.includes('budget-hotel:ratepkg-1'), false, `${status} with generic gap cannot tombstone logical offer`);
81
+ assert.equal(afterGap.hotels['budget-hotel']?.tombstonedOfferVersionRefs.includes(versionFor('budget-hotel:ratepkg-1')), false, `${status} with generic gap cannot tombstone version`);
82
+ }
83
+ let queryBudget = recordOfferCheckIssued(createAvailabilityPolicy(unavailableWorkspace), unavailableWorkspace, 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 'query-budget-check');
84
+ queryBudget = recordOfferCheckReceipt(queryBudget, ws(1, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), receipt('query-budget-check', 1, 'budget-hotel:ratepkg-1'), 'query-budget-check', 'budget-hotel:ratepkg-1', versionFor('budget-hotel:ratepkg-1'), 0);
85
+ for (const [actionId, revision] of [['query-budget-1', 2], ['query-budget-2', 3]]) {
86
+ const offerRef = `budget-hotel:query-ratepkg-${revision}`;
87
+ queryBudget = recordOffersQueryIssued(queryBudget, ['budget-hotel'], ws(revision - 1, ['budget-hotel'], [[offerRef, 'budget-hotel']]), actionId);
88
+ const queryReceipt = offersReceipt(actionId, revision, ['budget-hotel'], [offerRef], 1);
89
+ queryBudget = recordOffersGeneration(queryBudget, ['budget-hotel'], ws(revision, ['budget-hotel'], [[offerRef, 'budget-hotel']]), actionId, digest(queryReceipt), queryReceipt);
90
+ }
91
+ assert.equal(queryBudget.hotels['budget-hotel']?.offerQueriesIssued, 2, 'two refresh queries are retained as lifetime usage');
92
+ assert.throws(() => recordOffersQueryIssued(queryBudget, ['budget-hotel'], ws(3, ['budget-hotel'], [['budget-hotel:ratepkg-1', 'budget-hotel']]), 'query-budget-3'), /availability_offer_query_limit_reached/, 'third offers.query is rejected');
93
+ const hotels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
94
+ const candidateWorkspace = ws(0, hotels, hotels.map((hotelRef, index) => [`candidate-${index + 1}`, hotelRef]), ['candidate-6', 'candidate-1', 'candidate-2', 'candidate-3', 'candidate-4', 'candidate-5']);
95
+ const candidates = recordOfferCheckIssued(createAvailabilityPolicy(candidateWorkspace), candidateWorkspace, 'candidate-6', versionFor('candidate-6'), 'candidate-check');
96
+ assert.deepEqual(candidates.hotelRefs, ['h6', 'h1', 'h2', 'h3', 'h4'], 'selected hotel is first and recovery freezes at five candidates');
97
+ assert.equal(candidates.hotelRefs.includes('h5'), false, 'sixth shortlisted hotel never enters the recovery epoch');
98
+ const duplicateWorkspace = ws(0, ['dup-a', 'dup-b'], [['dup-offer', 'dup-a'], ['dup-offer', 'dup-b']]);
99
+ assert.throws(() => createAvailabilityPolicy(duplicateWorkspace), /availability_duplicate_offer_ref/, 'loaded OfferRef must be globally unique before any .find-based authority lookup');
100
+ assert.throws(() => canIssueOfferCheck(createAvailabilityPolicy(ws(0, ['dup-a'], [])), duplicateWorkspace, 'dup-offer', versionFor('dup-offer')), /availability_duplicate_offer_ref/);
101
+ }
102
+ // Locked cap and source cases: a selected RoomList offer wins ordering, while
103
+ // a complete recovery generation never admits more than three offers.
104
+ {
105
+ const roomList = ws(0, ['room-hotel'], [['room-1', 'room-hotel'], ['room-2', 'room-hotel'], ['room-3', 'room-hotel'], ['room-4', 'room-hotel']], ['room-4']);
106
+ const selected = recordOfferCheckIssued(createAvailabilityPolicy(roomList), roomList, 'room-4', versionFor('room-4'), 'room-check');
107
+ assert.deepEqual(selected.hotels['room-hotel']?.currentOfferRefs, ['room-4', 'room-1', 'room-2']);
108
+ assert.equal(selected.hotels['room-hotel']?.currentGeneration?.source.kind, 'workspace_snapshot');
109
+ const complete = ws(0, ['complete-hotel'], [['complete-1', 'complete-hotel']]);
110
+ const completeStarted = recordOfferCheckIssued(createAvailabilityPolicy(complete), complete, 'complete-1', versionFor('complete-1'), 'complete-check');
111
+ const completeQuery = recordOffersQueryIssued(completeStarted, ['complete-hotel'], complete, 'complete-query', digest(criteria), criteria);
112
+ const fourOfferWorkspace = ws(1, ['complete-hotel'], [['complete-1', 'complete-hotel'], ['complete-2', 'complete-hotel'], ['complete-3', 'complete-hotel'], ['complete-4', 'complete-hotel']]);
113
+ assert.throws(() => recordOffersGeneration(completeQuery, ['complete-hotel'], fourOfferWorkspace, 'complete-query', digest(offersReceipt('complete-query', 1, ['complete-hotel'], ['complete-1', 'complete-2', 'complete-3', 'complete-4'], 1)), offersReceipt('complete-query', 1, ['complete-hotel'], ['complete-1', 'complete-2', 'complete-3', 'complete-4'], 1), digest(criteria), criteria), /availability_offer_limit_exceeded/);
114
+ }
115
+ // Locked partial/empty reconciliation: a partial receipt may only project an
116
+ // evidenced subset, and a clean empty receipt must agree with workspace truth.
117
+ {
118
+ const partialWorkspace = ws(0, ['partial-hotel'], [['partial-1', 'partial-hotel'], ['partial-2', 'partial-hotel'], ['partial-3', 'partial-hotel']]);
119
+ const partialStarted = recordOfferCheckIssued(createAvailabilityPolicy(partialWorkspace), partialWorkspace, 'partial-1', versionFor('partial-1'), 'partial-check');
120
+ const partialQuery = recordOffersQueryIssued(partialStarted, ['partial-hotel'], partialWorkspace, 'partial-query', digest(criteria), criteria);
121
+ const partialReceipt = offersReceipt('partial-query', 1, ['partial-hotel'], ['partial-2'], 1, 'partial', 'partial', false);
122
+ const partial = recordOffersGeneration(partialQuery, ['partial-hotel'], ws(1, ['partial-hotel'], [['partial-1', 'partial-hotel'], ['partial-2', 'partial-hotel'], ['partial-3', 'partial-hotel']]), 'partial-query', digest(partialReceipt), partialReceipt, digest(criteria), criteria);
123
+ assert.equal(partial.hotels['partial-hotel']?.currentGeneration?.evidence, 'partial');
124
+ assert.equal(partial.hotels['partial-hotel']?.currentOfferRefs.length, 0);
125
+ assert.equal(partial.hotels['partial-hotel']?.freshOffersRequired, true);
126
+ assert.throws(() => recordOffersGeneration(partialQuery, ['partial-hotel'], ws(1, ['partial-hotel'], [['partial-1', 'partial-hotel'], ['partial-2', 'partial-hotel']]), 'partial-query', digest(offersReceipt('partial-query', 1, ['partial-hotel'], ['partial-1'], 2, 'partial', 'partial', false)), offersReceipt('partial-query', 1, ['partial-hotel'], ['partial-1'], 2, 'partial', 'partial', false), digest(criteria), criteria), /availability_offers_count_mismatch/);
127
+ const emptyWorkspace = ws(0, ['empty-hotel'], [['empty-1', 'empty-hotel']]);
128
+ const emptyStarted = recordOfferCheckIssued(createAvailabilityPolicy(emptyWorkspace), emptyWorkspace, 'empty-1', versionFor('empty-1'), 'empty-check');
129
+ const emptyQuery = recordOffersQueryIssued(emptyStarted, ['empty-hotel'], emptyWorkspace, 'empty-query');
130
+ const empty = offersReceipt('empty-query', 1, ['empty-hotel'], [], 0, 'no_match', 'empty', false);
131
+ assert.throws(() => recordOffersGeneration(emptyQuery, ['empty-hotel'], emptyWorkspace, 'empty-query', digest(empty), empty), /availability_offers_workspace_mismatch/);
132
+ }
133
+ // Locked provenance and verification cases: failed ordinary queries erase the
134
+ // seed, while a CheckAvail claim is confirmed only by the current workspace's
135
+ // verifiedOffer capability; changed is inconclusive and requires a fresh generation.
136
+ {
137
+ const seedWorkspace = ws(0, ['seed-hotel'], [['seed-1', 'seed-hotel']]);
138
+ const seed = offersReceipt('seed-query', 1, ['seed-hotel'], ['seed-1'], 1);
139
+ const seeded = recordObservedOffersQuery(createAvailabilityPolicy(seedWorkspace), ['seed-hotel'], digest(criteria), seedWorkspace, seed, 'seed-query', digest(seed), criteria);
140
+ assert.equal(seeded.criteriaDigest, digest(criteria));
141
+ const cleared = recordObservedOffersQuery(seeded, ['seed-hotel'], digest(criteria), ws(2, ['seed-hotel'], []), gapReceipt('failed-query', 2), 'failed-query', digest(gapReceipt('failed-query', 2)), criteria);
142
+ assert.equal(cleared.criteria, undefined);
143
+ assert.equal(cleared.criteriaDigest, undefined);
144
+ assert.equal(cleared.lastQuerySourceActionId, undefined);
145
+ const verificationWorkspace = ws(0, ['verify-hotel'], [['verify-1', 'verify-hotel']]);
146
+ const verificationStarted = recordOfferCheckIssued(createAvailabilityPolicy(verificationWorkspace), verificationWorkspace, 'verify-1', versionFor('verify-1'), 'verify-check');
147
+ const wrongVerified = { ...verificationWorkspace, revision: 1, verifiedOffer: verifiedCapability('other-offer', versionFor('other-offer')) };
148
+ const wrong = recordOfferCheckReceipt(verificationStarted, wrongVerified, receipt('verify-check', 1, 'verify-1', 'applied', true), 'verify-check', 'verify-1', versionFor('verify-1'), 0);
149
+ assert.notEqual(wrong.terminal?.code, 'availability_confirmed');
150
+ assert.equal(wrong.hotels['verify-hotel']?.status, 'inconclusive');
151
+ const matchingStarted = recordOfferCheckIssued(createAvailabilityPolicy(verificationWorkspace), verificationWorkspace, 'verify-1', versionFor('verify-1'), 'verify-match');
152
+ const matchingWorkspace = { ...verificationWorkspace, revision: 1, verifiedOffer: verifiedCapability('verify-1') };
153
+ const matching = recordOfferCheckReceipt(matchingStarted, matchingWorkspace, receipt('verify-match', 1, 'verify-1', 'applied', true), 'verify-match', 'verify-1', versionFor('verify-1'), 0);
154
+ assert.equal(matching.terminal?.code, 'availability_confirmed');
155
+ const verificationSeedReceipt = offersReceipt('verify-seed', 0, ['verify-hotel'], ['verify-1'], 1);
156
+ const verificationSeed = recordObservedOffersQuery(createAvailabilityPolicy(verificationWorkspace), ['verify-hotel'], digest(criteria), verificationWorkspace, verificationSeedReceipt, 'verify-seed', digest(verificationSeedReceipt), criteria);
157
+ const changedStarted = recordOfferCheckIssued(verificationSeed, verificationWorkspace, 'verify-1', versionFor('verify-1'), 'verify-changed');
158
+ const changedReceipt = { ...receipt('verify-changed', 1, 'verify-1', 'changed', true, versionFor('verify-1'), versionFor('verify-1', 'v2')), observation: { kind: 'offer.availability', offerRef: 'verify-1', checkedOfferVersionRef: versionFor('verify-1'), currentOfferVersionRef: versionFor('verify-1', 'v2'), available: true, changedFactRefs: ['price'], gapCodes: [] }, resultContract: { outcome: 'partial', hardCriteriaMet: false, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] } };
159
+ const changed = recordOfferCheckReceipt(changedStarted, { ...verificationWorkspace, revision: 1, loadedOffers: [{ offerRef: 'verify-1', offerVersionRef: versionFor('verify-1', 'v2'), hotelRef: 'verify-hotel', evidenceLevel: 'rate_loaded', factRefs: [] }] }, changedReceipt, 'verify-changed', 'verify-1', versionFor('verify-1'), 0);
160
+ assert.equal(changed.hotels['verify-hotel']?.status, 'active');
161
+ assert.deepEqual(changed.hotels['verify-hotel']?.currentOfferRefs, ['verify-1']);
162
+ assert.equal(changed.hotels['verify-hotel']?.tombstonedOfferVersionRefs.includes(versionFor('verify-1')), true);
163
+ assert.equal(changed.hotels['verify-hotel']?.freshOffersRequired, false);
164
+ }
165
+ const initial = ws(0, ['h1', 'h2'], [['o1', 'h1'], ['o2', 'h1'], ['b1', 'h2']], ['b1']);
166
+ assert.throws(() => recordOffersQueryIssued(createAvailabilityPolicy(initial), ['h1'], initial, 'pre-q'), /recovery_not_started/);
167
+ let state = recordOfferCheckIssued(createAvailabilityPolicy(initial), initial, 'o1', versionFor('o1'), 'check-1');
168
+ assert.deepEqual(state.hotelRefs, ['h1', 'h2']);
169
+ assert.equal(state.hotels.h1?.offerQueriesIssued, 0);
170
+ state = recordOfferCheckReceipt(state, ws(1, ['h1', 'h2'], [['o1', 'h1'], ['o2', 'h1'], ['b1', 'h2']]), receipt('check-1', 1, 'o1'), 'check-1', 'o1', versionFor('o1'), 0);
171
+ assert.equal(state.hotels.h1?.freshOffersRequired, true);
172
+ assert.equal(canIssueOfferCheck(state, ws(1, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'b1', versionFor('b1')).ok, false);
173
+ let q = recordOffersQueryIssued(state, ['h1'], ws(1, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'q1', 'criteria-a');
174
+ assert.throws(() => recordOffersQueryIssued(q, ['h1'], ws(1, ['h1', 'h2'], []), 'q2', 'criteria-b'), /criteria_changed/);
175
+ const staleQueryReceipt = { ...receipt('q1', 2, 'o2', 'stale'), observation: { kind: 'gap', code: 'stale_revision', factRefs: [] } };
176
+ q = foldQuery(q, ['h1'], ws(2, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'q1', staleQueryReceipt, 'criteria-a');
177
+ assert.equal(q.hotels.h1?.generationNo, 1);
178
+ assert.equal(q.hotels.h1?.currentGeneration?.generationId, 'h1:generation:1');
179
+ const completeQueryReceipt = { ...receipt('q2', 3, 'o2', 'applied'), observation: { kind: 'offers.state', hotelRefs: ['h1'], offerRefs: ['o2'], loadedHotelCount: 1 } };
180
+ q = recordOffersQueryIssued(q, ['h1'], ws(2, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'q2', 'criteria-a');
181
+ q = foldQuery(q, ['h1'], ws(3, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'q2', completeQueryReceipt, 'criteria-a');
182
+ assert.deepEqual(q.hotels.h1?.currentOfferRefs, ['o2']);
183
+ q = recordOfferCheckIssued(q, ws(3, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), 'o2', versionFor('o2'), 'check-2');
184
+ q = recordOfferCheckReceipt(q, ws(4, ['h1', 'h2'], [['o2', 'h1'], ['b1', 'h2']]), receipt('check-2', 4, 'o2'), 'check-2', 'o2', versionFor('o2'), 3);
185
+ assert.deepEqual(q.hotels.h1?.invalidatedOfferRefs, ['o2']);
186
+ assert.equal(q.hotels.h1?.freshOffersRequired, false);
187
+ assert.equal(q.terminal, undefined);
188
+ assert.throws(() => recordOffersQueryIssued(q, ['h1'], ws(4, ['h1', 'h2'], [['b1', 'h2']]), 'q-after-two-checks', 'criteria-a'), /hotel_not_active/);
189
+ const fw = ws(0, ['a', 'b'], [['a:ratepkg-1', 'a'], ['b:ratepkg-1', 'b']], ['b:ratepkg-1']);
190
+ let fallback = recordOfferCheckIssued(createAvailabilityPolicy(fw), fw, 'a:ratepkg-1', versionFor('a:ratepkg-1'), 'fa');
191
+ fallback = recordOfferCheckReceipt(fallback, ws(1, ['a', 'b'], [['a:ratepkg-1', 'a'], ['b:ratepkg-1', 'b']], ['b:ratepkg-1']), receipt('fa', 1, 'a:ratepkg-1'), 'fa', 'a:ratepkg-1', versionFor('a:ratepkg-1'), 0);
192
+ assert.equal(fallback.hotels.a?.freshOffersRequired, true);
193
+ fallback = recordOffersQueryIssued(fallback, ['a'], ws(1, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'fq1');
194
+ const fallbackEmpty = { ...receipt('fq1', 2, 'a:ratepkg-1', 'no_match'), observation: { kind: 'offers.state', hotelRefs: ['a'], offerRefs: [], loadedHotelCount: 0 } };
195
+ fallback = foldQuery(fallback, ['a'], ws(2, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'fq1', fallbackEmpty);
196
+ assert.equal(fallback.activeHotelOrdinal, 1);
197
+ assert.equal(canIssueOfferCheck(fallback, ws(2, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'b:ratepkg-1', versionFor('b:ratepkg-1')).ok, false);
198
+ fallback = recordOffersQueryIssued(fallback, ['b'], ws(2, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'fq2');
199
+ const fallbackOffers = { ...receipt('fq2', 3, 'b:ratepkg-1', 'applied'), observation: { kind: 'offers.state', hotelRefs: ['b'], offerRefs: ['b:ratepkg-1'], loadedHotelCount: 1 } };
200
+ fallback = foldQuery(fallback, ['b'], ws(3, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'fq2', fallbackOffers);
201
+ assert.equal(canIssueOfferCheck(fallback, ws(3, ['a', 'b'], [['b:ratepkg-1', 'b']]), 'b:ratepkg-1', versionFor('b:ratepkg-1')).ok, true);
202
+ let exhausted = recordOfferCheckIssued(createAvailabilityPolicy(ws(0, ['x'], [['x1', 'x']])), ws(0, ['x'], [['x1', 'x']]), 'x1', versionFor('x1'), 'ex1');
203
+ exhausted = recordOfferCheckReceipt(exhausted, ws(1, ['x'], [['x1', 'x']]), receipt('ex1', 1, 'x1'), 'ex1', 'x1', versionFor('x1'), 0);
204
+ assert.equal(exhausted.terminal, undefined);
205
+ assert.equal(exhausted.hotels.x?.freshOffersRequired, true);
206
+ exhausted = recordOffersQueryIssued(exhausted, ['x'], ws(1, ['x'], [['x1', 'x']]), 'exq');
207
+ const exhaustedReceipt = { ...receipt('exq', 2, 'x1', 'no_match'), observation: { kind: 'offers.state', hotelRefs: ['x'], offerRefs: [], loadedHotelCount: 0 } };
208
+ exhausted = foldQuery(exhausted, ['x'], ws(2, ['x'], []), 'exq', exhaustedReceipt);
209
+ assert.equal(exhausted.hotels.x?.generationNo, 2);
210
+ assert.equal(exhausted.terminal?.code, 'availability_exhausted_complete');
211
+ assert.throws(() => recordOfferCheckIssued(exhausted, ws(2, ['x'], []), 'x1', versionFor('x1'), 'after-terminal'), /availability_terminal/);
212
+ // A failed A@v1 must not poison a same-hotel candidate query containing B@v1.
213
+ // The reducer filters A's tombstone inside the generation, admits only B, and
214
+ // issues the next CheckAvail without getting stuck on the old offer.
215
+ {
216
+ const candidateWorkspace = ws(0, ['candidate-hotel'], [['candidate-a', 'candidate-hotel'], ['candidate-b', 'candidate-hotel']], ['candidate-a', 'candidate-b']);
217
+ let candidateState = recordOfferCheckIssued(createAvailabilityPolicy(candidateWorkspace), candidateWorkspace, 'candidate-a', versionFor('candidate-a'), 'candidate-a-check');
218
+ candidateState = recordOfferCheckReceipt(candidateState, ws(1, ['candidate-hotel'], [['candidate-a', 'candidate-hotel'], ['candidate-b', 'candidate-hotel']], ['candidate-a', 'candidate-b']), receipt('candidate-a-check', 1, 'candidate-a'), 'candidate-a-check', 'candidate-a', versionFor('candidate-a'), 0);
219
+ const queryWorkspace = ws(1, ['candidate-hotel'], [['candidate-a', 'candidate-hotel'], ['candidate-b', 'candidate-hotel']], ['candidate-a', 'candidate-b']);
220
+ const candidateQueryReceipt = offersReceipt('candidate-query', 2, ['candidate-hotel'], ['candidate-a', 'candidate-b'], 1);
221
+ candidateState = recordOffersQueryIssued(candidateState, ['candidate-hotel'], queryWorkspace, 'candidate-query');
222
+ candidateState = recordOffersGeneration(candidateState, ['candidate-hotel'], queryWorkspace, 'candidate-query', digest(candidateQueryReceipt), candidateQueryReceipt);
223
+ assert.equal(candidateState.hotels['candidate-hotel']?.currentOfferRefs.includes('candidate-a'), false, 'unavailable A@v1 is not re-admitted by a same-hotel candidate query');
224
+ assert.deepEqual(candidateState.hotels['candidate-hotel']?.currentOfferRefs, ['candidate-b'], 'same-hotel candidate query admits B@v1 only');
225
+ assert.equal(candidateState.activeHotelOrdinal, 0, 'same-hotel recovery remains on the active hotel after filtering A');
226
+ const bWorkspace = ws(2, ['candidate-hotel'], [['candidate-b', 'candidate-hotel']], ['candidate-b']);
227
+ assert.equal(canIssueOfferCheck(candidateState, bWorkspace, 'candidate-b', versionFor('candidate-b')).ok, true, 'B@v1 is recheckable after A@v1 unavailable');
228
+ candidateState = recordOfferCheckIssued(candidateState, bWorkspace, 'candidate-b', versionFor('candidate-b'), 'candidate-b-check');
229
+ const bVerified = verifiedCapability('candidate-b', versionFor('candidate-b'));
230
+ candidateState = recordOfferCheckReceipt(candidateState, ws(3, ['candidate-hotel'], [['candidate-b', 'candidate-hotel']], ['candidate-b'], bVerified), receipt('candidate-b-check', 3, 'candidate-b', 'applied', true), 'candidate-b-check', 'candidate-b', versionFor('candidate-b'), 2);
231
+ assert.equal(candidateState.terminal?.code, 'availability_confirmed', 'recheck of B@v1 can confirm after A@v1 unavailable');
232
+ }
233
+ console.log('BOOKING COPILOT AVAILABILITY POLICY: admission, criteria CAS, stale no-generation, invalidation, cursor fallback, exhaustion, absorbing terminal OK');
234
+ // Runtime proof: action replay, restart restoration, terminal SSE batch and absorbing seams.
235
+ const ledgerRoot = mkdtempSync(join(tmpdir(), 'gotry-availability-proof-'));
236
+ const ledger = ensureLedger(ledgerRoot);
237
+ const runtime = new BookingCopilotTaskRuntime(ledger, { contextRefFactory: () => 'ctx-availability' });
238
+ const runtimeWorkspace = ws(0, ['rh'], []);
239
+ runtime.startTask({ schemaVersion: 'booking.surface', kind: 'user.turn', taskId: 'runtime-availability', turnId: 'turn-1', workspace: runtimeWorkspace, request: { text: 'find rates' } });
240
+ const query = { schemaVersion: 'booking.surface', kind: 'offers.query', actionId: 'rq-1', contextRef: 'ctx-availability', expectedRevision: 0, reason: 'ordinary query', factRefs: [], input: { hotelRefs: ['rh'], criteria: {} } };
241
+ runtime.issueOperation('runtime-availability', query, offerVerifiedIntent);
242
+ runtime.continueWithReceipt({ schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: 'runtime-availability', workspace: ws(1, ['rh'], [['ro1', 'rh']]), receipt: { schemaVersion: 'booking.surface', kind: 'action.receipt', actionId: 'rq-1', contextRef: 'ctx-availability', status: 'applied', revision: 1, observation: { kind: 'offers.state', hotelRefs: ['rh'], offerRefs: ['ro1'], loadedHotelCount: 1 }, resultContract: { outcome: 'complete', hardCriteriaMet: true, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] } } });
243
+ const check = { schemaVersion: 'booking.surface', kind: 'offer.check', actionId: 'rc-1', contextRef: 'ctx-availability', expectedRevision: 1, reason: 'CheckAvail', factRefs: [], input: { offerRef: 'ro1', offerVersionRef: versionFor('ro1') } };
244
+ runtime.issueOperation('runtime-availability', check, offerVerifiedIntent);
245
+ runtime.issueOperation('runtime-availability', check, offerVerifiedIntent);
246
+ assert.equal(runtime.resumeTask('runtime-availability')?.availability.attempts.length, 1);
247
+ const restarted = new BookingCopilotTaskRuntime(ensureLedger(ledgerRoot), { contextRefFactory: () => 'ctx-availability' });
248
+ assert.equal(restarted.resumeTask('runtime-availability')?.availability.attempts[0]?.actionId, 'rc-1');
249
+ runtime.continueWithReceipt({ schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: 'runtime-availability', workspace: ws(2, ['rh'], [['ro1', 'rh']]), receipt: { ...receipt('rc-1', 2, 'ro1'), status: 'unavailable' } });
250
+ const query2 = { ...query, actionId: 'rq-2', expectedRevision: 2 };
251
+ runtime.issueOperation('runtime-availability', query2, offerVerifiedIntent);
252
+ runtime.continueWithReceipt({ schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: 'runtime-availability', workspace: ws(3, ['rh'], []), receipt: { schemaVersion: 'booking.surface', kind: 'action.receipt', actionId: 'rq-2', contextRef: 'ctx-availability', status: 'no_match', revision: 3, observation: { kind: 'offers.state', hotelRefs: ['rh'], offerRefs: [], loadedHotelCount: 0 }, resultContract: { outcome: 'empty', hardCriteriaMet: false, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] } } });
253
+ assert.equal(runtime.resumeTask('runtime-availability')?.phase, 'terminal');
254
+ const eventCount = ledger.countEvents();
255
+ const terminalBatchPayload = ledger.db.prepare("SELECT payload FROM events WHERE kind = 'booking.copilot.decision.batch' ORDER BY seq DESC LIMIT 1").get();
256
+ const terminalBatch = JSON.parse(terminalBatchPayload.payload);
257
+ assert.deepEqual(runtime.terminalDecisionBatch('runtime-availability', terminalBatch.requestKey), terminalBatch.events);
258
+ assert.deepEqual(runtime.terminalDecisionBatch('runtime-availability', terminalBatch.requestKey), terminalBatch.events, 'exact terminal batch replay is read-only');
259
+ assert.throws(() => runtime.terminalDecisionBatch('runtime-availability', 'arbitrary-new-terminal-key'), /task_terminal/);
260
+ assert.throws(() => runtime.issueOperation('runtime-availability', { ...query, actionId: 'late', expectedRevision: 3 }, offerVerifiedIntent), /task_terminal/);
261
+ assert.throws(() => runtime.emitEvent('runtime-availability', { kind: 'status', status: 'working' }), /task_terminal/);
262
+ assert.equal(ledger.countEvents(), eventCount);
263
+ ledger.close();
264
+ restarted['ledger'].close();
265
+ rmSync(ledgerRoot, { recursive: true, force: true });
@@ -0,0 +1,66 @@
1
+ /** Production executable proof for the standalone Booking Copilot server. */
2
+ import assert from 'node:assert/strict';
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join, resolve } from 'node:path';
7
+ const root = resolve(import.meta.dirname, '../..');
8
+ const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
9
+ assert.equal(packageJson.bin?.['gotry-booking-copilot'], 'bin/gotry-booking-copilot.js');
10
+ const build = spawnSync(process.execPath, ['scripts/build-dist.mjs'], {
11
+ cwd: root,
12
+ encoding: 'utf8',
13
+ });
14
+ assert.equal(build.status, 0, build.stderr || build.stdout);
15
+ const bin = join(root, 'bin/gotry-booking-copilot.js');
16
+ const missingKey = spawnSync(process.execPath, [bin], {
17
+ cwd: root,
18
+ env: { PATH: process.env.PATH },
19
+ encoding: 'utf8',
20
+ });
21
+ assert.equal(missingKey.status, 1);
22
+ assert.match(missingKey.stderr, /booking_copilot_api_key_required/);
23
+ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-bin-'));
24
+ try {
25
+ const child = spawn(process.execPath, [bin], {
26
+ cwd: root,
27
+ env: {
28
+ PATH: process.env.PATH,
29
+ GOTRY_BOOKING_COPILOT_API_KEY: 'fixture-bff-key',
30
+ GOTRY_BOOKING_COPILOT_STATE_ROOT: stateRoot,
31
+ GOTRY_BOOKING_COPILOT_HOST: '127.0.0.1',
32
+ GOTRY_BOOKING_COPILOT_PORT: '0',
33
+ DEEPSEEK_API_KEY: 'fixture-model-key',
34
+ },
35
+ stdio: ['ignore', 'pipe', 'pipe'],
36
+ });
37
+ let stderr = '';
38
+ child.stderr.setEncoding('utf8');
39
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
40
+ await new Promise((resolveReady, reject) => {
41
+ const timeout = setTimeout(() => reject(new Error(`booking copilot bin did not listen: ${stderr}`)), 10_000);
42
+ const inspect = () => {
43
+ if (!stderr.includes('/a2a/booking-copilot/turn'))
44
+ return;
45
+ clearTimeout(timeout);
46
+ resolveReady();
47
+ };
48
+ child.stderr.on('data', inspect);
49
+ child.once('exit', (code) => {
50
+ clearTimeout(timeout);
51
+ reject(new Error(`booking copilot bin exited before listen: ${code}: ${stderr}`));
52
+ });
53
+ });
54
+ assert.match(stderr, /listening on http:\/\/127\.0\.0\.1:\d+\/a2a\/booking-copilot\/turn/);
55
+ assert.ok(!stderr.includes('fixture-bff-key'));
56
+ assert.ok(!stderr.includes('fixture-model-key'));
57
+ child.kill('SIGTERM');
58
+ const exit = await new Promise((resolveExit) => {
59
+ child.once('exit', (code, signal) => resolveExit({ code, signal }));
60
+ });
61
+ assert.deepEqual(exit, { code: 0, signal: null }, `graceful shutdown failed: ${stderr}`);
62
+ }
63
+ finally {
64
+ rmSync(stateRoot, { recursive: true, force: true });
65
+ }
66
+ console.log('BOOKING COPILOT BIN PROOF: startup failure/listen/SIGTERM lifecycle OK');
@@ -0,0 +1,76 @@
1
+ import assert from 'node:assert/strict';
2
+ /**
3
+ * Keep a body failure while attempting every independent cleanup operation.
4
+ * Each attempt is awaited in dependency order, while its failure is retained
5
+ * so a later cleanup attempt still runs.
6
+ */
7
+ export async function throwWithCleanupFailures(bodyFailed, bodyError, cleanupAttempts, settlement) {
8
+ const cleanupErrors = [];
9
+ for (const attempt of cleanupAttempts) {
10
+ try {
11
+ await attempt();
12
+ }
13
+ catch (error) {
14
+ cleanupErrors.push(error);
15
+ }
16
+ }
17
+ try {
18
+ await (cleanupErrors.length === 0 ? settlement?.onSuccess : settlement?.onFailure)?.();
19
+ }
20
+ catch (error) {
21
+ cleanupErrors.push(error);
22
+ }
23
+ if (!bodyFailed && cleanupErrors.length === 0)
24
+ return;
25
+ if (bodyFailed && cleanupErrors.length === 0)
26
+ throw bodyError;
27
+ throw new AggregateError(bodyFailed ? [bodyError, ...cleanupErrors] : cleanupErrors, 'booking_copilot_proof_cleanup_failed');
28
+ }
29
+ if (process.argv[2] === '--negative-control') {
30
+ const cleanupOrder = [];
31
+ const bodyError = new Error('proof body failure');
32
+ const plannerCleanupError = new Error('proof planner cleanup failure');
33
+ const portCleanupError = new Error('proof port cleanup failure');
34
+ let legacyCaught;
35
+ try {
36
+ try {
37
+ throw bodyError;
38
+ }
39
+ finally {
40
+ cleanupOrder.push('legacy-planner');
41
+ throw plannerCleanupError;
42
+ }
43
+ }
44
+ catch (error) {
45
+ legacyCaught = error;
46
+ }
47
+ assert.equal(legacyCaught, plannerCleanupError, 'legacy sequential finally masks the body error');
48
+ assert.deepEqual(cleanupOrder, ['legacy-planner'], 'legacy sequential finally skips cleanup after the first failure');
49
+ cleanupOrder.length = 0;
50
+ let caught;
51
+ try {
52
+ await throwWithCleanupFailures(true, bodyError, [
53
+ async () => { cleanupOrder.push('planner'); throw plannerCleanupError; },
54
+ async () => { cleanupOrder.push('port'); throw portCleanupError; },
55
+ async () => { cleanupOrder.push('server'); },
56
+ ], {
57
+ onSuccess: () => { cleanupOrder.push('state-deleted'); },
58
+ onFailure: () => { cleanupOrder.push('state-retained'); },
59
+ });
60
+ }
61
+ catch (error) {
62
+ caught = error;
63
+ }
64
+ assert.ok(caught instanceof AggregateError, 'body and cleanup failures are aggregated');
65
+ assert.deepEqual(caught.errors, [bodyError, plannerCleanupError, portCleanupError], 'body error remains first and all cleanup errors remain');
66
+ assert.deepEqual(cleanupOrder, ['planner', 'port', 'server', 'state-retained'], 'later cleanup runs and the failed tree keeps its evidence');
67
+ let cleanStateRemoved = false;
68
+ await assert.rejects(throwWithCleanupFailures(true, bodyError, [async () => { }], {
69
+ onSuccess: () => { cleanStateRemoved = true; },
70
+ }), (error) => error === bodyError);
71
+ assert.equal(cleanStateRemoved, true, 'successful cleanup removes state even when the body failed');
72
+ console.log(JSON.stringify({
73
+ legacy: { caught: plannerCleanupError.message, cleanupOrder: ['legacy-planner'] },
74
+ repaired: { body: bodyError.message, cleanup: [plannerCleanupError.message, portCleanupError.message], cleanupOrder },
75
+ }));
76
+ }