@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,3 +1,10 @@
1
+ /**
2
+ * BFF-only typed HTTP/SSE seam proof.
3
+ *
4
+ * Run with Node 24 from the repository root:
5
+ * npx --yes --package=node@24 --package=tsx --call \
6
+ * 'tsx ts/scripts/booking-copilot-server-proof-tests.ts'
7
+ */
1
8
  import assert from 'node:assert/strict';
2
9
  import { mkdtempSync, rmSync } from 'node:fs';
3
10
  import { tmpdir } from 'node:os';
@@ -14,20 +21,19 @@ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-server-'));
14
21
  const ledger = ensureLedger(stateRoot);
15
22
  let nextId = 0;
16
23
  const runtime = new BookingCopilotTaskRuntime(ledger, {
17
- idFactory: (prefix)=>`${prefix}-${++nextId}`,
18
- now: ()=>'2026-08-30T12:00:00.000Z'
24
+ idFactory: (prefix) => `${prefix}-${++nextId}`,
25
+ now: () => '2026-08-30T12:00:00.000Z',
19
26
  });
20
27
  let factoryCalls = 0;
21
28
  let sessionTurns = 0;
22
- const plannerFactory = (initialTask)=>{
29
+ const plannerFactory = (initialTask) => {
23
30
  factoryCalls += 1;
24
31
  assert.equal(initialTask.taskId, 'task-http-1');
25
32
  return {
26
- async next ({ task }) {
33
+ async next({ task }) {
27
34
  sessionTurns += 1;
28
35
  if (sessionTurns === 1) {
29
- return [
30
- {
36
+ return [{
31
37
  kind: 'operation',
32
38
  action: {
33
39
  schemaVersion: 'booking.surface',
@@ -37,36 +43,28 @@ const plannerFactory = (initialTask)=>{
37
43
  expectedRevision: task.revision,
38
44
  reason: 'Run the authoritative workspace search',
39
45
  factRefs: [],
40
- input: {}
41
- }
42
- }
43
- ];
46
+ input: {},
47
+ },
48
+ }];
44
49
  }
45
50
  return [
46
51
  {
47
52
  kind: 'explanation',
48
- explanation: {
49
- text: '{"kind":"search.run","input":{}}',
50
- factRefs: []
51
- }
53
+ explanation: { text: '{"kind":"search.run","input":{}}', factRefs: [] },
52
54
  },
53
55
  {
54
56
  kind: 'terminal',
55
- terminal: {
56
- status: 'completed',
57
- summary: 'Stopped at the requested search result.',
58
- factRefs: []
59
- }
60
- }
57
+ terminal: { status: 'completed', summary: 'Stopped at the requested search result.', factRefs: [] },
58
+ },
61
59
  ];
62
- }
60
+ },
63
61
  };
64
62
  };
65
63
  const serverHandle = await startBookingCopilotServer({
66
64
  apiKey: API_KEY,
67
65
  runtime,
68
66
  plannerFactory,
69
- artifactId: ARTIFACT_ID
67
+ artifactId: ARTIFACT_ID,
70
68
  });
71
69
  const endpoint = `http://127.0.0.1:${serverHandle.port}/a2a/booking-copilot/turn`;
72
70
  const expectedProbe = {
@@ -74,10 +72,7 @@ const expectedProbe = {
74
72
  schemaSha256: SCHEMA_SHA256,
75
73
  status: 'ready',
76
74
  ingressMode: 'bff-bound-turn-only',
77
- acceptedTurnKinds: [
78
- 'user.turn',
79
- 'action.receipt.continuation'
80
- ]
75
+ acceptedTurnKinds: ['user.turn', 'action.receipt.continuation'],
81
76
  };
82
77
  const runtimeReport = process.report?.getReport?.();
83
78
  const runtimeGlibcVersion = runtimeReport?.header?.glibcVersionRuntime ?? '';
@@ -86,29 +81,15 @@ await assert.rejects(startBookingCopilotServer({
86
81
  apiKey: API_KEY,
87
82
  runtime,
88
83
  plannerFactory,
89
- artifactId: 'not-a-commit'
84
+ artifactId: 'not-a-commit',
90
85
  }), /booking_copilot_artifact_id_invalid/);
91
- for (const path of [
92
- '/healthz',
93
- '/status'
94
- ]){
86
+ for (const path of ['/healthz', '/status']) {
95
87
  const probeUrl = `http://127.0.0.1:${serverHandle.port}${path}`;
96
- for (const authorization of [
97
- undefined,
98
- 'Bearer wrong-key'
99
- ]){
100
- const rejected = await fetch(probeUrl, authorization ? {
101
- headers: {
102
- authorization
103
- }
104
- } : undefined);
88
+ for (const authorization of [undefined, 'Bearer wrong-key']) {
89
+ const rejected = await fetch(probeUrl, authorization ? { headers: { authorization } } : undefined);
105
90
  assert.equal(rejected.status, 401, `${path} rejects ${authorization ? 'wrong' : 'missing'} bearer`);
106
91
  }
107
- const authorized = await fetch(probeUrl, {
108
- headers: {
109
- authorization: `Bearer ${API_KEY}`
110
- }
111
- });
92
+ const authorized = await fetch(probeUrl, { headers: { authorization: `Bearer ${API_KEY}` } });
112
93
  assert.equal(authorized.status, 200, `${path} accepts deployment bearer`);
113
94
  assert.equal(authorized.headers.get('x-booking-surface-version'), SCHEMA_VERSION, `${path} returns canonical schema version header`);
114
95
  assert.equal(authorized.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256, `${path} returns canonical schema hash header`);
@@ -127,18 +108,14 @@ const workspace = {
127
108
  locale: 'zh-CN',
128
109
  currency: 'AED',
129
110
  searchDraft: {},
130
- results: {
131
- status: 'idle'
132
- },
111
+ results: { status: 'idle' },
133
112
  visibleHotels: [],
134
113
  loadedOffers: [],
135
114
  shortlistedOfferRefs: [],
136
115
  capabilities: {
137
116
  surface: 'tenant',
138
- allowedActions: [
139
- 'search.run'
140
- ]
141
- }
117
+ allowedActions: ['search.run'],
118
+ },
142
119
  };
143
120
  const userTurn = {
144
121
  schemaVersion: 'booking.surface',
@@ -146,36 +123,27 @@ const userTurn = {
146
123
  taskId: 'task-http-1',
147
124
  turnId: 'http-turn-1',
148
125
  workspace,
149
- request: {
150
- text: '执行当前搜索'
151
- }
126
+ request: { text: '执行当前搜索' },
152
127
  };
153
128
  const post = (body, authorization = `Bearer ${API_KEY}`, schemaHeaders = {
154
129
  'x-booking-surface-version': SCHEMA_VERSION,
155
- 'x-booking-surface-schema-sha256': SCHEMA_SHA256
156
- })=>fetch(endpoint, {
157
- method: 'POST',
158
- headers: {
159
- authorization,
160
- 'content-type': 'application/json',
161
- accept: 'text/event-stream',
162
- ...schemaHeaders
163
- },
164
- body: JSON.stringify(body)
165
- });
130
+ 'x-booking-surface-schema-sha256': SCHEMA_SHA256,
131
+ }) => fetch(endpoint, {
132
+ method: 'POST',
133
+ headers: { authorization, 'content-type': 'application/json', accept: 'text/event-stream', ...schemaHeaders },
134
+ body: JSON.stringify(body),
135
+ });
166
136
  const noAuth = await fetch(endpoint, {
167
137
  method: 'POST',
168
- headers: {
169
- 'content-type': 'application/json'
170
- },
171
- body: JSON.stringify(userTurn)
138
+ headers: { 'content-type': 'application/json' },
139
+ body: JSON.stringify(userTurn),
172
140
  });
173
141
  assert.equal(noAuth.status, 401, 'server-to-server key is fail-closed');
174
142
  const missingSchemaHeaders = await post(userTurn, `Bearer ${API_KEY}`, {});
175
143
  assert.equal(missingSchemaHeaders.status, 409, 'schema version/hash headers are required');
176
144
  const wrongSchemaHash = await post(userTurn, `Bearer ${API_KEY}`, {
177
145
  'x-booking-surface-version': SCHEMA_VERSION,
178
- 'x-booking-surface-schema-sha256': '0'.repeat(64)
146
+ 'x-booking-surface-schema-sha256': '0'.repeat(64),
179
147
  });
180
148
  assert.equal(wrongSchemaHash.status, 409, 'schema drift is rejected before planning');
181
149
  const directIngress = await post({
@@ -184,28 +152,14 @@ const directIngress = await post({
184
152
  requestKey: 'browser-request-1',
185
153
  surfaceHint: 'tenant',
186
154
  workspace: {
187
- schemaVersion: 'booking.surface',
188
- revision: 0,
189
- locale: 'zh-CN',
190
- currency: 'AED',
191
- searchDraft: {},
192
- results: {
193
- status: 'idle'
194
- },
195
- visibleHotels: [],
196
- loadedOffers: [],
197
- shortlistedOfferRefs: []
155
+ schemaVersion: 'booking.surface', revision: 0, locale: 'zh-CN', currency: 'AED',
156
+ searchDraft: {}, results: { status: 'idle' }, visibleHotels: [], loadedOffers: [], shortlistedOfferRefs: [],
198
157
  },
199
- request: {
200
- text: 'browser must use same-origin BFF'
201
- }
158
+ request: { text: 'browser must use same-origin BFF' },
202
159
  });
203
160
  assert.equal(directIngress.status, 503, 'GoTry server rejects unbound browser ingress');
204
161
  assert.equal((await directIngress.json()).error.code, 'trusted_ingress_binding_required');
205
- const tokenSmuggle = await post({
206
- ...userTurn,
207
- portalToken: 'ST:must-not-cross-boundary'
208
- });
162
+ const tokenSmuggle = await post({ ...userTurn, portalToken: 'ST:must-not-cross-boundary' });
209
163
  assert.equal(tokenSmuggle.status, 400, 'strict planner turn rejects portal token fields');
210
164
  const first = await post(userTurn);
211
165
  assert.equal(first.status, 200);
@@ -213,21 +167,16 @@ assert.match(String(first.headers.get('content-type')), /text\/event-stream/);
213
167
  assert.equal(first.headers.get('x-booking-surface-version'), SCHEMA_VERSION);
214
168
  assert.equal(first.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256);
215
169
  function parseSse(body) {
216
- return body.trim().split('\n\n').map((frame)=>{
217
- const data = frame.split('\n').find((line)=>line.startsWith('data: '));
170
+ return body.trim().split('\n\n').map((frame) => {
171
+ const data = frame.split('\n').find((line) => line.startsWith('data: '));
218
172
  assert.ok(data, `SSE frame has data: ${frame}`);
219
173
  return JSON.parse(data.slice('data: '.length));
220
174
  });
221
175
  }
222
176
  const firstEvents = parseSse(await first.text());
223
- assert.deepEqual(firstEvents.map((event)=>event.kind), [
224
- 'status',
225
- 'status',
226
- 'operation',
227
- 'status'
228
- ]);
177
+ assert.deepEqual(firstEvents.map((event) => event.kind), ['status', 'status', 'operation', 'status']);
229
178
  assert.equal(firstEvents[3].kind === 'status' ? firstEvents[3].status : '', 'waiting_receipt', 'the operation leaves the task durably waiting for its receipt');
230
- assert.ok(firstEvents.every((event)=>event.contextRef === workspace.contextRef), 'every typed event carries BFF-minted contextRef');
179
+ assert.ok(firstEvents.every((event) => event.contextRef === workspace.contextRef), 'every typed event carries BFF-minted contextRef');
231
180
  const operation = firstEvents[2];
232
181
  assert.equal(operation?.kind, 'operation');
233
182
  assert.equal(factoryCalls, 1);
@@ -239,11 +188,7 @@ const receiptTurn = {
239
188
  workspace: {
240
189
  ...workspace,
241
190
  revision: 1,
242
- results: {
243
- status: 'ready',
244
- resultCount: 2,
245
- searchSessionRef: 'search-http-1'
246
- }
191
+ results: { status: 'ready', resultCount: 2, searchSessionRef: 'search-http-1' },
247
192
  },
248
193
  receipt: {
249
194
  schemaVersion: 'booking.surface',
@@ -252,37 +197,22 @@ const receiptTurn = {
252
197
  contextRef: workspace.contextRef,
253
198
  status: 'applied',
254
199
  revision: 1,
255
- observation: {
256
- kind: 'search.state',
257
- searchSessionRef: 'search-http-1',
258
- resultCount: 2
259
- },
260
- resultContract: {
261
- outcome: 'complete',
262
- hardCriteriaMet: true,
263
- factRefs: [],
264
- gapCodes: [],
265
- blockers: [],
266
- relaxationsApplied: []
267
- }
268
- }
200
+ observation: { kind: 'search.state', searchSessionRef: 'search-http-1', resultCount: 2 },
201
+ resultContract: { outcome: 'complete', hardCriteriaMet: true, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] },
202
+ },
269
203
  };
270
204
  const second = await post(receiptTurn);
271
205
  assert.equal(second.status, 200);
272
206
  const secondEvents = parseSse(await second.text());
273
- assert.deepEqual(secondEvents.map((event)=>event.kind), [
274
- 'status',
275
- 'explanation',
276
- 'terminal'
277
- ]);
207
+ assert.deepEqual(secondEvents.map((event) => event.kind), ['status', 'explanation', 'terminal']);
278
208
  assert.equal(factoryCalls, 1, 'one task reuses one planner session across receipt continuations');
279
209
  assert.equal(sessionTurns, 2);
280
- assert.ok(!secondEvents.some((event)=>event.kind === 'operation'), 'JSON-looking explanation text is not parsed into an operation');
210
+ assert.ok(!secondEvents.some((event) => event.kind === 'operation'), 'JSON-looking explanation text is not parsed into an operation');
281
211
  assert.ok(secondEvents[0].sequence > firstEvents.at(-1).sequence, 'SSE sequence is monotonic across turns');
282
212
  await serverHandle.close();
283
213
  const preRestartRuntime = new BookingCopilotTaskRuntime(ledger, {
284
- idFactory: (prefix)=>`${prefix}-probe-${++nextId}`,
285
- now: ()=>'2026-08-30T12:01:00.000Z'
214
+ idFactory: (prefix) => `${prefix}-probe-${++nextId}`,
215
+ now: () => '2026-08-30T12:01:00.000Z',
286
216
  });
287
217
  const recovered = preRestartRuntime.resumeTask('task-http-1');
288
218
  assert.equal(recovered?.lastReceipt?.actionId, 'action-http-1', 'new server process seam restores task from last typed receipt');
@@ -290,23 +220,10 @@ assert.equal(recovered?.phase, 'terminal', 'the completed task stays durably ter
290
220
  const restarted = await startBookingCopilotServer({
291
221
  apiKey: API_KEY,
292
222
  runtime: preRestartRuntime,
293
- plannerFactory: (initialTask)=>{
223
+ plannerFactory: (initialTask) => {
294
224
  assert.equal(initialTask.taskId, 'task-http-2');
295
- return {
296
- async next () {
297
- return [
298
- {
299
- kind: 'terminal',
300
- terminal: {
301
- status: 'stopped',
302
- summary: 'Recovered.',
303
- factRefs: []
304
- }
305
- }
306
- ];
307
- }
308
- };
309
- }
225
+ return { async next() { return [{ kind: 'terminal', terminal: { status: 'stopped', summary: 'Recovered.', factRefs: [] } }]; } };
226
+ },
310
227
  });
311
228
  const restartedEndpoint = `http://127.0.0.1:${restarted.port}/a2a/booking-copilot/turn`;
312
229
  const afterRestartResponse = await fetch(restartedEndpoint, {
@@ -316,37 +233,15 @@ const afterRestartResponse = await fetch(restartedEndpoint, {
316
233
  'content-type': 'application/json',
317
234
  accept: 'text/event-stream',
318
235
  'x-booking-surface-version': SCHEMA_VERSION,
319
- 'x-booking-surface-schema-sha256': SCHEMA_SHA256
236
+ 'x-booking-surface-schema-sha256': SCHEMA_SHA256,
320
237
  },
321
- body: JSON.stringify({
322
- ...userTurn,
323
- taskId: 'task-http-2',
324
- turnId: 'http-restart-turn-1',
325
- workspace: {
326
- ...workspace,
327
- contextRef: 'ctx-http-2'
328
- }
329
- })
238
+ body: JSON.stringify({ ...userTurn, taskId: 'task-http-2', turnId: 'http-restart-turn-1', workspace: { ...workspace, contextRef: 'ctx-http-2' } }),
330
239
  });
331
240
  assert.equal(afterRestartResponse.status, 200);
332
241
  const restartEvents = parseSse(await afterRestartResponse.text());
333
- assert.deepEqual(restartEvents.map((event)=>event.kind), [
334
- 'status',
335
- 'status',
336
- 'terminal'
337
- ]);
338
- assert.deepEqual(restartEvents.map((event)=>event.sequence), [
339
- 1,
340
- 2,
341
- 3
342
- ], 'the restarted process allocates fresh task-scoped sequences after recovering the ledger');
242
+ assert.deepEqual(restartEvents.map((event) => event.kind), ['status', 'status', 'terminal']);
243
+ assert.deepEqual(restartEvents.map((event) => event.sequence), [1, 2, 3], 'the restarted process allocates fresh task-scoped sequences after recovering the ledger');
343
244
  await restarted.close();
344
245
  ledger.close();
345
- rmSync(stateRoot, {
346
- recursive: true,
347
- force: true
348
- });
246
+ rmSync(stateRoot, { recursive: true, force: true });
349
247
  console.log('BOOKING COPILOT SERVER PROOF: BFF auth/typed SSE/task session/restart/no-token/no-text-action OK');
350
-
351
-
352
- //# sourceURL=ts/scripts/booking-copilot-server-proof-tests.ts
@@ -1,19 +1,20 @@
1
+ /** Production startup composition proof (dependencies injected; no real model). */
1
2
  import assert from 'node:assert/strict';
2
3
  import { mkdtempSync, rmSync } from 'node:fs';
3
4
  import { tmpdir } from 'node:os';
4
5
  import { join } from 'node:path';
5
- import { resolveBookingCopilotStartupConfig, startBookingCopilotFromEnvironment } from '../src/booking-surface/startup.js';
6
+ import { resolveBookingCopilotStartupConfig, startBookingCopilotFromEnvironment, } from '../src/booking-surface/startup.js';
6
7
  import { BOOKING_READ_ACTION_KINDS } from '../src/booking-surface/contracts.js';
7
- assert.throws(()=>resolveBookingCopilotStartupConfig({}), /booking_copilot_api_key_required/);
8
- assert.throws(()=>resolveBookingCopilotStartupConfig({
9
- GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
10
- GOTRY_BOOKING_COPILOT_STATE_ROOT: '/tmp/gotry-booking-startup',
11
- GOTRY_BOOKING_COPILOT_ARTIFACT_ID: 'not-a-commit'
12
- }), /booking_copilot_artifact_id_invalid/);
13
- assert.throws(()=>resolveBookingCopilotStartupConfig({
14
- GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
15
- GOTRY_BOOKING_COPILOT_STATE_ROOT: 'relative/state'
16
- }), /booking_copilot_state_root_must_be_absolute/);
8
+ assert.throws(() => resolveBookingCopilotStartupConfig({}), /booking_copilot_api_key_required/);
9
+ assert.throws(() => resolveBookingCopilotStartupConfig({
10
+ GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
11
+ GOTRY_BOOKING_COPILOT_STATE_ROOT: '/tmp/gotry-booking-startup',
12
+ GOTRY_BOOKING_COPILOT_ARTIFACT_ID: 'not-a-commit',
13
+ }), /booking_copilot_artifact_id_invalid/);
14
+ assert.throws(() => resolveBookingCopilotStartupConfig({
15
+ GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
16
+ GOTRY_BOOKING_COPILOT_STATE_ROOT: 'relative/state',
17
+ }), /booking_copilot_state_root_must_be_absolute/);
17
18
  const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-'));
18
19
  const env = {
19
20
  GOTRY_BOOKING_COPILOT_API_KEY: 'bff-only-key',
@@ -22,134 +23,57 @@ const env = {
22
23
  GOTRY_BOOKING_COPILOT_ARTIFACT_ID: '1111111111111111111111111111111111111111',
23
24
  LLM_API_KEY: 'model-only-key',
24
25
  PORTAL_TOKEN: 'must-not-enter-planner',
25
- HOTELBYTE_TOKEN: 'must-not-enter-planner'
26
+ HOTELBYTE_TOKEN: 'must-not-enter-planner',
26
27
  };
27
- await assert.rejects(startBookingCopilotFromEnvironment({
28
- ...env,
29
- GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding'
30
- }, {}), /booking_copilot_ingress_binding_required/, 'explicit ingress mode rejects startup when the trusted BFF seam is absent');
28
+ await assert.rejects(startBookingCopilotFromEnvironment({ ...env, GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding' }, {}), /booking_copilot_ingress_binding_required/, 'explicit ingress mode rejects startup when the trusted BFF seam is absent');
31
29
  await assert.rejects(startBookingCopilotFromEnvironment(env, {
32
- ingressBinding: {
33
- bind: ()=>({
34
- taskId: 'partial-task',
35
- turnId: 'partial-turn',
36
- contextRef: 'partial-context',
37
- surface: 'tenant',
38
- allowedActions: [
39
- ...BOOKING_READ_ACTION_KINDS
40
- ]
41
- })
42
- }
30
+ ingressBinding: { bind: () => ({ taskId: 'partial-task', turnId: 'partial-turn', contextRef: 'partial-context', surface: 'tenant', allowedActions: [...BOOKING_READ_ACTION_KINDS] }) },
43
31
  }), /booking_copilot_ingress_binding_pair_required/, 'partial trusted BFF seam is rejected before startup');
44
- await assert.rejects(startBookingCopilotFromEnvironment(env, {
45
- principal: {
46
- subject: 'partial-bff',
47
- scope: 'booking:read'
48
- }
49
- }), /booking_copilot_ingress_binding_pair_required/, 'principal without a binding is rejected before startup');
32
+ await assert.rejects(startBookingCopilotFromEnvironment(env, { principal: { subject: 'partial-bff', scope: 'booking:read' } }), /booking_copilot_ingress_binding_pair_required/, 'principal without a binding is rejected before startup');
50
33
  let standaloneIngressMode = '';
51
34
  const standaloneStateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-standalone-'));
52
- const standalone = await startBookingCopilotFromEnvironment({
53
- ...env,
54
- GOTRY_BOOKING_COPILOT_STATE_ROOT: standaloneStateRoot
55
- }, {
56
- ensureLedger () {
57
- return {
58
- close () {}
59
- };
60
- },
61
- runtimeFactory () {
62
- return {};
63
- },
64
- createPlanner: async ()=>({
65
- plannerFactory () {
66
- return {
67
- async next () {
68
- return [];
69
- }
70
- };
71
- },
72
- async close () {}
73
- }),
74
- async startServer (options) {
35
+ const standalone = await startBookingCopilotFromEnvironment({ ...env, GOTRY_BOOKING_COPILOT_STATE_ROOT: standaloneStateRoot }, {
36
+ ensureLedger() { return { close() { } }; },
37
+ runtimeFactory() { return {}; },
38
+ createPlanner: async () => ({ plannerFactory() { return { async next() { return []; } }; }, async close() { } }),
39
+ async startServer(options) {
75
40
  standaloneIngressMode = options.ingressMode ?? '';
76
- return {
77
- server: {},
78
- port: 43122,
79
- async close () {}
80
- };
81
- }
41
+ return { server: {}, port: 43122, async close() { } };
42
+ },
82
43
  });
83
44
  assert.equal(standalone.port, 43122);
84
45
  assert.equal(standaloneIngressMode, 'bff-bound-turn-only', 'standalone production startup uses the explicit bound-turn-only mode');
85
46
  await standalone.close();
86
- rmSync(standaloneStateRoot, {
87
- recursive: true,
88
- force: true
89
- });
47
+ rmSync(standaloneStateRoot, { recursive: true, force: true });
90
48
  const closeOrder = [];
91
49
  let plannerEnv;
92
50
  let serverApiKey = '';
93
51
  let serverArtifactId = '';
94
- const trustedEnv = {
95
- ...env,
96
- GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding'
97
- };
98
- const fakeLedger = {
99
- close () {
100
- closeOrder.push('ledger');
101
- }
102
- };
103
- const trustedBinding = {
104
- bind: ()=>({
105
- taskId: 'startup-task',
106
- turnId: 'startup-turn',
107
- contextRef: 'startup-context',
108
- surface: 'tenant',
109
- allowedActions: [
110
- ...BOOKING_READ_ACTION_KINDS
111
- ]
112
- })
113
- };
52
+ const trustedEnv = { ...env, GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding' };
53
+ const fakeLedger = { close() { closeOrder.push('ledger'); } };
54
+ const trustedBinding = { bind: () => ({ taskId: 'startup-task', turnId: 'startup-turn', contextRef: 'startup-context', surface: 'tenant', allowedActions: [...BOOKING_READ_ACTION_KINDS] }) };
114
55
  const started = await startBookingCopilotFromEnvironment(trustedEnv, {
115
- ensureLedger () {
116
- return fakeLedger;
117
- },
118
- runtimeFactory () {
119
- return {};
120
- },
121
- async createPlanner (options) {
56
+ ensureLedger() { return fakeLedger; },
57
+ runtimeFactory() { return {}; },
58
+ async createPlanner(options) {
122
59
  plannerEnv = options.env;
123
60
  return {
124
- plannerFactory () {
125
- return {
126
- async next () {
127
- return [];
128
- }
129
- };
130
- },
131
- async close () {
132
- closeOrder.push('planner');
133
- }
61
+ plannerFactory() { return { async next() { return []; } }; },
62
+ async close() { closeOrder.push('planner'); },
134
63
  };
135
64
  },
136
65
  ingressBinding: trustedBinding,
137
- principal: {
138
- subject: 'startup-bff',
139
- scope: 'booking:read'
140
- },
141
- async startServer (options) {
66
+ principal: { subject: 'startup-bff', scope: 'booking:read' },
67
+ async startServer(options) {
142
68
  serverApiKey = options.apiKey;
143
69
  serverArtifactId = options.artifactId ?? '';
144
70
  assert.equal(options.ingressMode, 'bff-ingress-binding');
145
71
  return {
146
72
  server: {},
147
73
  port: 43123,
148
- async close () {
149
- closeOrder.push('server');
150
- }
74
+ async close() { closeOrder.push('server'); },
151
75
  };
152
- }
76
+ },
153
77
  });
154
78
  assert.equal(started.port, 43123);
155
79
  assert.equal(serverApiKey, 'bff-only-key', 'BFF deployment key terminates at the HTTP server');
@@ -159,16 +83,6 @@ assert.equal(plannerEnv?.GOTRY_BOOKING_COPILOT_API_KEY, undefined);
159
83
  assert.equal(plannerEnv?.PORTAL_TOKEN, undefined);
160
84
  assert.equal(plannerEnv?.HOTELBYTE_TOKEN, undefined);
161
85
  await started.close();
162
- assert.deepEqual(closeOrder, [
163
- 'server',
164
- 'planner',
165
- 'ledger'
166
- ]);
167
- rmSync(stateRoot, {
168
- recursive: true,
169
- force: true
170
- });
86
+ assert.deepEqual(closeOrder, ['server', 'planner', 'ledger']);
87
+ rmSync(stateRoot, { recursive: true, force: true });
171
88
  console.log('BOOKING COPILOT STARTUP PROOF: env split/server+planner+ledger lifecycle OK');
172
-
173
-
174
- //# sourceURL=ts/scripts/booking-copilot-startup-proof-tests.ts
@@ -0,0 +1,80 @@
1
+ import { closeBackend, createBackendServer } from '../src/backend/kernel.js';
2
+ import { startBookingExecutorModule } from '../src/backend/modules/booking-executor.js';
3
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
+ let pass = 0;
5
+ let fail = 0;
6
+ const check = (cond, label) => {
7
+ if (cond) {
8
+ pass += 1;
9
+ console.log(` ok - ${label}`);
10
+ }
11
+ else {
12
+ fail += 1;
13
+ console.log(` FAIL - ${label}`);
14
+ }
15
+ };
16
+ async function jfetch(url, init) {
17
+ const r = await fetch(url, init);
18
+ let body = null;
19
+ try {
20
+ body = await r.json();
21
+ }
22
+ catch { /* 空体 */ }
23
+ return { status: r.status, body };
24
+ }
25
+ async function main() {
26
+ const module = startBookingExecutorModule({
27
+ apiKey: () => 'test-booking-key',
28
+ evidenceDir: () => '/tmp/booking-executor-test-evidence',
29
+ observe: async () => ({
30
+ ok: true,
31
+ supplier: 'dida-portal',
32
+ entryUrl: 'https://portal.dida.com/hotel/find',
33
+ entryPoints: [{ text: '预订', tag: 'button' }],
34
+ screenshotPath: '/tmp/evidence/test.png',
35
+ observedAt: new Date().toISOString(),
36
+ }),
37
+ });
38
+ const handle = await createBackendServer({ modules: [module], port: 0 });
39
+ const base = `http://127.0.0.1:${handle.port}`;
40
+ try {
41
+ // ① 鉴权 fail-closed
42
+ const noAuth = await jfetch(`${base}/v1/booking/observe`, {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/json' },
45
+ body: JSON.stringify({ supplier: 'dida-portal' }),
46
+ });
47
+ check(noAuth.status === 403, '无 token 403');
48
+ // ② 注入观察器:正常观察
49
+ const ok = await jfetch(`${base}/v1/booking/observe`, {
50
+ method: 'POST',
51
+ headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
52
+ body: JSON.stringify({ supplier: 'dida-portal' }),
53
+ });
54
+ const okBody = ok.body;
55
+ check(ok.status === 200 && okBody.ok === true && okBody.supplier === 'dida-portal', '观察返回 200 + 供应商');
56
+ check(Array.isArray(okBody.entryPoints) && okBody.entryPoints.length === 1, '入口观察条目形状');
57
+ // ③ 未知供应商拒绝
58
+ const bad = await jfetch(`${base}/v1/booking/observe`, {
59
+ method: 'POST',
60
+ headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
61
+ body: JSON.stringify({ supplier: 'ctrip-flight' }),
62
+ });
63
+ check(bad.status === 400, '未知供应商 400');
64
+ // ④ 域外 entryUrl 拒绝
65
+ const evil = await jfetch(`${base}/v1/booking/observe`, {
66
+ method: 'POST',
67
+ headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
68
+ body: JSON.stringify({ supplier: 'dida-portal', entryUrl: 'https://evil.example.com/x' }),
69
+ });
70
+ check(evil.status === 400, '域外 entryUrl 400');
71
+ }
72
+ finally {
73
+ await closeBackend(handle, [module]).catch(() => { });
74
+ }
75
+ await sleep(30);
76
+ console.log(`\nBOOKING EXECUTOR: ${pass} pass, ${fail} fail`);
77
+ if (fail > 0)
78
+ process.exitCode = 1;
79
+ }
80
+ void main();