@danceiny/gotry 0.0.1-rc.16 → 0.0.1-rc.18

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 (229) hide show
  1. package/README.md +161 -116
  2. package/README.zh-CN.md +173 -144
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +113 -66
  5. package/bin/gotry-inner.js +436 -70
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +5 -1
  10. package/dist/capabilities/agent-reach-deep.js +1 -1
  11. package/dist/capabilities/agent-reach.js +1 -1
  12. package/dist/capabilities/anything.js +1 -1
  13. package/dist/capabilities/artifacts.js +1 -1
  14. package/dist/capabilities/effect.js +1 -1
  15. package/dist/capabilities/fact-log.js +1 -1
  16. package/dist/capabilities/flyai.js +20 -6
  17. package/dist/capabilities/hbcli.js +1 -1
  18. package/dist/capabilities/incident-log.js +1 -1
  19. package/dist/capabilities/model-override.js +18 -0
  20. package/dist/capabilities/opensky.js +1 -1
  21. package/dist/capabilities/resilience.js +1 -1
  22. package/dist/capabilities/session/action-cache.js +1 -1
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  24. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  25. package/dist/capabilities/session/benchmark.js +1 -1
  26. package/dist/capabilities/session/extension-bridge.js +19 -6
  27. package/dist/capabilities/session/extension-channel.js +3 -3
  28. package/dist/capabilities/session/extension-distribution.js +234 -0
  29. package/dist/capabilities/session/extract.js +1 -1
  30. package/dist/capabilities/session/golden-score.js +92 -0
  31. package/dist/capabilities/session/health-watch.js +1 -1
  32. package/dist/capabilities/session/read-guard.js +1 -1
  33. package/dist/capabilities/session/static-flight-golden.js +137 -0
  34. package/dist/capabilities/session/transport.js +1 -1
  35. package/dist/capabilities/session/wizard.js +21 -251
  36. package/dist/capabilities/session-consent.js +1 -1
  37. package/dist/capabilities/session-login.js +11 -5
  38. package/dist/capabilities/session-search.js +46 -4
  39. package/dist/capabilities/weather.js +168 -46
  40. package/dist/data/session-golden-20.json +25 -0
  41. package/dist/data/sf-golden-manifest.json +102 -0
  42. package/dist/data/sf-static-routes.json +91 -0
  43. package/dist/scripts/action-cache-tests.js +1 -1
  44. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  45. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  46. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  47. package/dist/scripts/agent-reach-tests.js +1 -1
  48. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  49. package/dist/scripts/anything-tests.js +1 -1
  50. package/dist/scripts/async-collect.js +1 -1
  51. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  52. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  53. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  54. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  55. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  56. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  57. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  58. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  59. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  60. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  61. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  62. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  63. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  64. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  65. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  66. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  67. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  68. package/dist/scripts/booking-saga-tests.js +1 -1
  69. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  70. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  71. package/dist/scripts/bootstrap-tests.js +45 -39
  72. package/dist/scripts/build-changelog.js +1 -1
  73. package/dist/scripts/changelog-tests.js +1 -1
  74. package/dist/scripts/companion-tests.js +1 -1
  75. package/dist/scripts/diff-test.js +1 -1
  76. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  77. package/dist/scripts/dsh-runtime-closure.js +150 -0
  78. package/dist/scripts/effect-tests.js +1 -1
  79. package/dist/scripts/engine-run.js +1 -1
  80. package/dist/scripts/engine-tests.js +1 -1
  81. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  82. package/dist/scripts/evaluation-contract-tests.js +574 -0
  83. package/dist/scripts/extension-distribution-cli.js +35 -0
  84. package/dist/scripts/extension-distribution-tests.js +341 -0
  85. package/dist/scripts/extension-tests.js +128 -23
  86. package/dist/scripts/fact-gate-tests.js +1 -1
  87. package/dist/scripts/flyai-tests.js +1 -1
  88. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  89. package/dist/scripts/hbcli-tests.js +1 -1
  90. package/dist/scripts/health-watch-cli.js +1 -1
  91. package/dist/scripts/i18n-tests.js +1 -1
  92. package/dist/scripts/incident-tests.js +1 -1
  93. package/dist/scripts/journey-tests.js +1 -1
  94. package/dist/scripts/ledger-tests.js +1 -1
  95. package/dist/scripts/ledger-workflow-crash.js +1 -1
  96. package/dist/scripts/memory-capture-tests.js +1 -1
  97. package/dist/scripts/memory-decay-tests.js +1 -1
  98. package/dist/scripts/memory-metrics.js +1 -1
  99. package/dist/scripts/memory-value-report.js +1 -1
  100. package/dist/scripts/model-override-e2e.js +176 -0
  101. package/dist/scripts/nightly-evidence-tests.js +1 -1
  102. package/dist/scripts/nightly-evidence.js +1 -1
  103. package/dist/scripts/nudge-digest.js +1 -1
  104. package/dist/scripts/onboarding-tests.js +21 -53
  105. package/dist/scripts/opensky-check.js +1 -1
  106. package/dist/scripts/opensky-tests.js +1 -1
  107. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  108. package/dist/scripts/price-drift-tests.js +1 -1
  109. package/dist/scripts/price-drift-watch.js +1 -1
  110. package/dist/scripts/probe-poi-tests.js +1 -1
  111. package/dist/scripts/product-metrics.js +1 -1
  112. package/dist/scripts/publish-preverify.js +40 -4
  113. package/dist/scripts/realtime-pricing-tests.js +1 -1
  114. package/dist/scripts/replay-async.js +1 -1
  115. package/dist/scripts/replay-real.js +1 -1
  116. package/dist/scripts/replay.js +1 -1
  117. package/dist/scripts/session-attach-diagnose.js +1 -1
  118. package/dist/scripts/session-attach-poc.js +1 -1
  119. package/dist/scripts/session-benchmark.js +1 -1
  120. package/dist/scripts/session-extract-tests.js +1 -1
  121. package/dist/scripts/session-login.js +1 -1
  122. package/dist/scripts/session-tests.js +70 -20
  123. package/dist/scripts/sf-live-benchmark.js +338 -0
  124. package/dist/scripts/sf-live-cli-tests.js +21 -0
  125. package/dist/scripts/sf-soft-score-tests.js +108 -0
  126. package/dist/scripts/sf-summary.js +93 -0
  127. package/dist/scripts/skeleton-check.js +1 -1
  128. package/dist/scripts/skeleton-integration-test.js +1 -1
  129. package/dist/scripts/skills-contract-tests.js +1 -1
  130. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  131. package/dist/scripts/smoke.js +79 -33
  132. package/dist/scripts/state-cli-tests.js +1 -1
  133. package/dist/scripts/state-cli.js +1 -1
  134. package/dist/scripts/static-golden-tests.js +299 -0
  135. package/dist/scripts/time-eval-tests.js +1 -1
  136. package/dist/scripts/travel-timeline-tests.js +1 -1
  137. package/dist/scripts/unified-tests.js +1 -1
  138. package/dist/scripts/weather-tests.js +694 -44
  139. package/dist/scripts/z3-race-tests.js +1 -1
  140. package/dist/src/artifact-gate.js +1 -1
  141. package/dist/src/benchmark-agent-conformance.js +370 -0
  142. package/dist/src/benchmark-environment-bridge.js +384 -0
  143. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  144. package/dist/src/benchmark-tool-isolation.js +124 -0
  145. package/dist/src/bookable-facts.js +1 -1
  146. package/dist/src/booking-saga.js +1 -1
  147. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  148. package/dist/src/booking-surface/canonical-schema.js +113 -0
  149. package/dist/src/booking-surface/contracts-v2.js +89 -0
  150. package/dist/src/booking-surface/contracts.js +46 -0
  151. package/dist/src/booking-surface/dsh-planner.js +453 -0
  152. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  153. package/dist/src/booking-surface/error-codes.js +94 -0
  154. package/dist/src/booking-surface/index.js +15 -0
  155. package/dist/src/booking-surface/profile.js +68 -0
  156. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  157. package/dist/src/booking-surface/runtime.js +351 -0
  158. package/dist/src/booking-surface/server-v2.js +334 -0
  159. package/dist/src/booking-surface/server.js +302 -0
  160. package/dist/src/booking-surface/startup.js +159 -0
  161. package/dist/src/booking-surface/validation-v2.js +319 -0
  162. package/dist/src/booking-surface/validation.js +809 -0
  163. package/dist/src/bridge.js +1 -1
  164. package/dist/src/companions.js +1 -1
  165. package/dist/src/contracts.js +1 -1
  166. package/dist/src/dsh-llm.js +1 -1
  167. package/dist/src/engine.js +1 -1
  168. package/dist/src/evaluation-cadence.js +234 -0
  169. package/dist/src/evaluation-contracts.js +906 -0
  170. package/dist/src/i18n.js +1 -1
  171. package/dist/src/index.js +50 -19
  172. package/dist/src/journey.js +1 -1
  173. package/dist/src/loop.js +1 -1
  174. package/dist/src/memory-capture.js +1 -1
  175. package/dist/src/memory-decay.js +1 -1
  176. package/dist/src/memory-utility.js +1 -1
  177. package/dist/src/mock-llm.js +1 -1
  178. package/dist/src/model.js +1 -1
  179. package/dist/src/realtime-pricing.js +1 -1
  180. package/dist/src/slot-spec.js +1 -1
  181. package/dist/src/state-ledger.js +2 -1
  182. package/dist/src/time-anchor.js +1 -1
  183. package/dist/src/tool-budget.js +136 -0
  184. package/dist/src/tool-packet.js +1 -1
  185. package/dist/src/travel-slots.js +1 -1
  186. package/dist/src/travel-timeline.js +1 -1
  187. package/dist/src/unified.js +1 -1
  188. package/dist/src/wish-pool.js +1 -1
  189. package/dist/src/z3-shared.js +1 -1
  190. package/extension/README.md +31 -7
  191. package/package.json +286 -11
  192. package/schemas/booking.surface.v1.schema.json +927 -0
  193. package/schemas/booking.surface.v2.schema.json +61 -0
  194. package/ts/capabilities/flyai.ts +16 -3
  195. package/ts/capabilities/session/extension-bridge.ts +37 -14
  196. package/ts/capabilities/session/extension-channel.ts +6 -3
  197. package/ts/capabilities/session/extension-distribution.ts +264 -0
  198. package/ts/capabilities/session/golden-score.ts +139 -0
  199. package/ts/capabilities/session/health-watch.ts +1 -1
  200. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  201. package/ts/capabilities/session/wizard.ts +34 -176
  202. package/ts/capabilities/session-login.ts +15 -5
  203. package/ts/capabilities/session-search.ts +40 -3
  204. package/ts/capabilities/weather.ts +141 -52
  205. package/ts/package.json +3 -3
  206. package/ts/src/benchmark-agent-conformance.ts +448 -0
  207. package/ts/src/benchmark-environment-bridge.ts +348 -0
  208. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  209. package/ts/src/benchmark-tool-isolation.ts +166 -0
  210. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  211. package/ts/src/booking-surface/canonical-schema.js +113 -0
  212. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  213. package/ts/src/booking-surface/contracts.ts +380 -0
  214. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  215. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  216. package/ts/src/booking-surface/error-codes.ts +101 -0
  217. package/ts/src/booking-surface/index.ts +12 -0
  218. package/ts/src/booking-surface/profile.ts +42 -0
  219. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  220. package/ts/src/booking-surface/runtime.ts +483 -0
  221. package/ts/src/booking-surface/server-v2.ts +247 -0
  222. package/ts/src/booking-surface/server.ts +324 -0
  223. package/ts/src/booking-surface/startup.ts +196 -0
  224. package/ts/src/booking-surface/validation-v2.ts +205 -0
  225. package/ts/src/booking-surface/validation.ts +453 -0
  226. package/ts/src/index.ts +64 -11
  227. package/ts/src/state-ledger.ts +1 -0
  228. package/ts/src/tool-budget.ts +165 -0
  229. package/dist/scripts/wizard-bootstrap.js +0 -32
@@ -0,0 +1,317 @@
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 { BookingCopilotTaskRuntime } from '../src/booking-surface/runtime.js';
7
+ import { startBookingCopilotServer } from '../src/booking-surface/server.js';
8
+ import { BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_VERSION } from '../src/booking-surface/contracts.js';
9
+ const API_KEY = 'server-to-server-key';
10
+ const SCHEMA_VERSION = BOOKING_SURFACE_SCHEMA_VERSION;
11
+ const SCHEMA_SHA256 = BOOKING_SURFACE_SCHEMA_SHA256;
12
+ const ARTIFACT_ID = '1111111111111111111111111111111111111111';
13
+ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-server-'));
14
+ const ledger = ensureLedger(stateRoot);
15
+ let nextId = 0;
16
+ const runtime = new BookingCopilotTaskRuntime(ledger, {
17
+ idFactory: (prefix)=>`${prefix}-${++nextId}`,
18
+ now: ()=>'2026-08-30T12:00:00.000Z'
19
+ });
20
+ let factoryCalls = 0;
21
+ let sessionTurns = 0;
22
+ const plannerFactory = (initialTask)=>{
23
+ factoryCalls += 1;
24
+ assert.equal(initialTask.taskId, 'task-http-1');
25
+ return {
26
+ async next ({ task }) {
27
+ sessionTurns += 1;
28
+ if (sessionTurns === 1) {
29
+ return [
30
+ {
31
+ kind: 'operation',
32
+ action: {
33
+ schemaVersion: 'booking.surface.v1',
34
+ kind: 'search.run',
35
+ actionId: 'action-http-1',
36
+ contextRef: task.contextRef,
37
+ expectedRevision: task.revision,
38
+ reason: 'Run the authoritative workspace search',
39
+ factRefs: [],
40
+ input: {}
41
+ }
42
+ }
43
+ ];
44
+ }
45
+ return [
46
+ {
47
+ kind: 'explanation',
48
+ explanation: {
49
+ text: '{"kind":"search.run","input":{}}',
50
+ factRefs: []
51
+ }
52
+ },
53
+ {
54
+ kind: 'terminal',
55
+ terminal: {
56
+ status: 'completed',
57
+ summary: 'Stopped at the requested search result.',
58
+ factRefs: []
59
+ }
60
+ }
61
+ ];
62
+ }
63
+ };
64
+ };
65
+ const serverHandle = await startBookingCopilotServer({
66
+ apiKey: API_KEY,
67
+ runtime,
68
+ plannerFactory,
69
+ artifactId: ARTIFACT_ID
70
+ });
71
+ const endpoint = `http://127.0.0.1:${serverHandle.port}/a2a/booking-copilot/turn`;
72
+ const expectedProbe = {
73
+ schemaVersion: SCHEMA_VERSION,
74
+ schemaSha256: SCHEMA_SHA256,
75
+ status: 'ready'
76
+ };
77
+ const runtimeReport = process.report?.getReport?.();
78
+ const runtimeGlibcVersion = runtimeReport?.header?.glibcVersionRuntime ?? '';
79
+ const runtimeReleaseTuple = `${process.platform}-${process.arch}-${runtimeGlibcVersion ? 'glibc' : 'unknown'}`;
80
+ await assert.rejects(startBookingCopilotServer({
81
+ apiKey: API_KEY,
82
+ runtime,
83
+ plannerFactory,
84
+ artifactId: 'not-a-commit'
85
+ }), /booking_copilot_artifact_id_invalid/);
86
+ for (const path of [
87
+ '/healthz',
88
+ '/status'
89
+ ]){
90
+ const probeUrl = `http://127.0.0.1:${serverHandle.port}${path}`;
91
+ for (const authorization of [
92
+ undefined,
93
+ 'Bearer wrong-key'
94
+ ]){
95
+ const rejected = await fetch(probeUrl, authorization ? {
96
+ headers: {
97
+ authorization
98
+ }
99
+ } : undefined);
100
+ assert.equal(rejected.status, 401, `${path} rejects ${authorization ? 'wrong' : 'missing'} bearer`);
101
+ }
102
+ const authorized = await fetch(probeUrl, {
103
+ headers: {
104
+ authorization: `Bearer ${API_KEY}`
105
+ }
106
+ });
107
+ assert.equal(authorized.status, 200, `${path} accepts deployment bearer`);
108
+ assert.equal(authorized.headers.get('x-booking-surface-version'), SCHEMA_VERSION, `${path} returns canonical schema version header`);
109
+ assert.equal(authorized.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256, `${path} returns canonical schema hash header`);
110
+ assert.equal(authorized.headers.get('x-gotry-artifact-id'), ARTIFACT_ID, `${path} returns the running release identity`);
111
+ assert.equal(authorized.headers.get('x-gotry-node-version'), process.version, `${path} returns the actual Node runtime`);
112
+ assert.equal(authorized.headers.get('x-gotry-node-modules-abi'), process.versions.modules, `${path} returns the actual native ABI`);
113
+ assert.equal(authorized.headers.get('x-gotry-release-tuple'), runtimeReleaseTuple, `${path} returns the actual runtime tuple`);
114
+ assert.equal(authorized.headers.get('x-gotry-glibc-version'), runtimeGlibcVersion || null, `${path} returns glibc only when present`);
115
+ assert.deepEqual(await authorized.json(), expectedProbe, `${path} exposes only typed readiness contract`);
116
+ }
117
+ const workspace = {
118
+ schemaVersion: 'booking.surface.v1',
119
+ contextRef: 'ctx-http-1',
120
+ surface: 'tenant',
121
+ revision: 0,
122
+ locale: 'zh-CN',
123
+ currency: 'AED',
124
+ searchDraft: {},
125
+ results: {
126
+ status: 'idle'
127
+ },
128
+ visibleHotels: [],
129
+ loadedOffers: [],
130
+ shortlistedOfferRefs: [],
131
+ capabilities: {
132
+ surface: 'tenant',
133
+ allowedActions: [
134
+ 'search.run'
135
+ ]
136
+ }
137
+ };
138
+ const userTurn = {
139
+ schemaVersion: 'booking.surface.v1',
140
+ kind: 'user.turn',
141
+ taskId: 'task-http-1',
142
+ workspace,
143
+ request: {
144
+ text: '执行当前搜索'
145
+ }
146
+ };
147
+ const post = (body, authorization = `Bearer ${API_KEY}`, schemaHeaders = {
148
+ 'x-booking-surface-version': SCHEMA_VERSION,
149
+ 'x-booking-surface-schema-sha256': SCHEMA_SHA256
150
+ })=>fetch(endpoint, {
151
+ method: 'POST',
152
+ headers: {
153
+ authorization,
154
+ 'content-type': 'application/json',
155
+ accept: 'text/event-stream',
156
+ ...schemaHeaders
157
+ },
158
+ body: JSON.stringify(body)
159
+ });
160
+ const noAuth = await fetch(endpoint, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'content-type': 'application/json'
164
+ },
165
+ body: JSON.stringify(userTurn)
166
+ });
167
+ assert.equal(noAuth.status, 401, 'server-to-server key is fail-closed');
168
+ const missingSchemaHeaders = await post(userTurn, `Bearer ${API_KEY}`, {});
169
+ assert.equal(missingSchemaHeaders.status, 409, 'schema version/hash headers are required');
170
+ const wrongSchemaHash = await post(userTurn, `Bearer ${API_KEY}`, {
171
+ 'x-booking-surface-version': SCHEMA_VERSION,
172
+ 'x-booking-surface-schema-sha256': '0'.repeat(64)
173
+ });
174
+ assert.equal(wrongSchemaHash.status, 409, 'schema drift is rejected before planning');
175
+ const directIngress = await post({
176
+ schemaVersion: 'booking.surface.v1',
177
+ kind: 'user.turn.ingress',
178
+ surfaceHint: 'tenant',
179
+ workspace: {
180
+ ...workspace,
181
+ contextRef: undefined,
182
+ surface: undefined,
183
+ capabilities: undefined
184
+ },
185
+ request: {
186
+ text: 'browser must use same-origin BFF'
187
+ }
188
+ });
189
+ assert.equal(directIngress.status, 400, 'GoTry server rejects unbound browser ingress');
190
+ const tokenSmuggle = await post({
191
+ ...userTurn,
192
+ portalToken: 'ST:must-not-cross-boundary'
193
+ });
194
+ assert.equal(tokenSmuggle.status, 400, 'strict planner turn rejects portal token fields');
195
+ const first = await post(userTurn);
196
+ assert.equal(first.status, 200);
197
+ assert.match(String(first.headers.get('content-type')), /text\/event-stream/);
198
+ assert.equal(first.headers.get('x-booking-surface-version'), SCHEMA_VERSION);
199
+ assert.equal(first.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256);
200
+ function parseSse(body) {
201
+ return body.trim().split('\n\n').map((frame)=>{
202
+ const data = frame.split('\n').find((line)=>line.startsWith('data: '));
203
+ assert.ok(data, `SSE frame has data: ${frame}`);
204
+ return JSON.parse(data.slice('data: '.length));
205
+ });
206
+ }
207
+ const firstEvents = parseSse(await first.text());
208
+ assert.deepEqual(firstEvents.map((event)=>event.kind), [
209
+ 'status',
210
+ 'status',
211
+ 'operation'
212
+ ]);
213
+ assert.ok(firstEvents.every((event)=>event.contextRef === workspace.contextRef), 'every typed event carries BFF-minted contextRef');
214
+ const operation = firstEvents[2];
215
+ assert.equal(operation?.kind, 'operation');
216
+ assert.equal(factoryCalls, 1);
217
+ assert.equal(sessionTurns, 1);
218
+ const receiptTurn = {
219
+ schemaVersion: 'booking.surface.v1',
220
+ kind: 'action.receipt.continuation',
221
+ taskId: 'task-http-1',
222
+ workspace: {
223
+ ...workspace,
224
+ revision: 1,
225
+ results: {
226
+ status: 'ready',
227
+ resultCount: 2,
228
+ searchSessionRef: 'search-http-1'
229
+ }
230
+ },
231
+ receipt: {
232
+ schemaVersion: 'booking.surface.v1',
233
+ kind: 'action.receipt',
234
+ actionId: 'action-http-1',
235
+ contextRef: workspace.contextRef,
236
+ status: 'applied',
237
+ revision: 1,
238
+ observation: {
239
+ kind: 'search.state',
240
+ searchSessionRef: 'search-http-1',
241
+ resultCount: 2
242
+ },
243
+ resultContract: {
244
+ outcome: 'complete',
245
+ hardCriteriaMet: true,
246
+ factRefs: [],
247
+ gapCodes: []
248
+ }
249
+ }
250
+ };
251
+ const second = await post(receiptTurn);
252
+ assert.equal(second.status, 200);
253
+ const secondEvents = parseSse(await second.text());
254
+ assert.deepEqual(secondEvents.map((event)=>event.kind), [
255
+ 'status',
256
+ 'explanation',
257
+ 'terminal'
258
+ ]);
259
+ assert.equal(factoryCalls, 1, 'one task reuses one planner session across receipt continuations');
260
+ assert.equal(sessionTurns, 2);
261
+ assert.ok(!secondEvents.some((event)=>event.kind === 'operation'), 'JSON-looking explanation text is not parsed into an operation');
262
+ assert.ok(secondEvents[0].sequence > firstEvents.at(-1).sequence, 'SSE sequence is monotonic across turns');
263
+ await serverHandle.close();
264
+ let recoveredLastReceipt = false;
265
+ const restarted = await startBookingCopilotServer({
266
+ apiKey: API_KEY,
267
+ runtime: new BookingCopilotTaskRuntime(ledger, {
268
+ idFactory: (prefix)=>`${prefix}-restart-${++nextId}`,
269
+ now: ()=>'2026-08-30T12:01:00.000Z'
270
+ }),
271
+ plannerFactory: (initialTask)=>{
272
+ recoveredLastReceipt = initialTask.lastReceipt?.actionId === 'action-http-1';
273
+ return {
274
+ async next () {
275
+ return [
276
+ {
277
+ kind: 'terminal',
278
+ terminal: {
279
+ status: 'stopped',
280
+ summary: 'Recovered.',
281
+ factRefs: []
282
+ }
283
+ }
284
+ ];
285
+ }
286
+ };
287
+ }
288
+ });
289
+ const restartedEndpoint = `http://127.0.0.1:${restarted.port}/a2a/booking-copilot/turn`;
290
+ const afterRestartResponse = await fetch(restartedEndpoint, {
291
+ method: 'POST',
292
+ headers: {
293
+ authorization: `Bearer ${API_KEY}`,
294
+ 'content-type': 'application/json',
295
+ accept: 'text/event-stream',
296
+ 'x-booking-surface-version': SCHEMA_VERSION,
297
+ 'x-booking-surface-schema-sha256': SCHEMA_SHA256
298
+ },
299
+ body: JSON.stringify({
300
+ ...userTurn,
301
+ workspace: receiptTurn.workspace
302
+ })
303
+ });
304
+ assert.equal(afterRestartResponse.status, 200);
305
+ assert.equal(recoveredLastReceipt, true, 'new server process seam restores task from last typed receipt');
306
+ const restartEvents = parseSse(await afterRestartResponse.text());
307
+ assert.ok(restartEvents[0].sequence > secondEvents.at(-1).sequence, 'sequence resumes from durable ledger after server restart');
308
+ await restarted.close();
309
+ ledger.close();
310
+ rmSync(stateRoot, {
311
+ recursive: true,
312
+ force: true
313
+ });
314
+ console.log('BOOKING COPILOT SERVER PROOF: BFF auth/typed SSE/task session/restart/no-token/no-text-action OK');
315
+
316
+
317
+ //# sourceURL=ts/scripts/booking-copilot-server-proof-tests.ts
@@ -0,0 +1,283 @@
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 { resolveBookingCopilotStartupConfig, startBookingCopilotFromEnvironment } from '../src/booking-surface/startup.js';
6
+ import { BOOKING_READ_ACTION_KINDS_V2 } from '../src/booking-surface/contracts-v2.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/);
17
+ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-'));
18
+ const env = {
19
+ GOTRY_BOOKING_COPILOT_API_KEY: 'bff-only-key',
20
+ GOTRY_BOOKING_COPILOT_STATE_ROOT: stateRoot,
21
+ GOTRY_BOOKING_COPILOT_PORT: '0',
22
+ GOTRY_BOOKING_COPILOT_ARTIFACT_ID: '1111111111111111111111111111111111111111',
23
+ LLM_API_KEY: 'model-only-key',
24
+ PORTAL_TOKEN: 'must-not-enter-planner',
25
+ HOTELBYTE_TOKEN: 'must-not-enter-planner'
26
+ };
27
+ await assert.rejects(startBookingCopilotFromEnvironment({
28
+ ...env,
29
+ GOTRY_BOOKING_COPILOT_V2_INGRESS_MODE: 'bff-ingress-binding'
30
+ }, {}), /booking_copilot_v2_ingress_binding_required/, 'explicit ingress mode rejects startup when the trusted BFF seam is absent');
31
+ 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_V2
40
+ ]
41
+ })
42
+ }
43
+ }), /booking_copilot_v2_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_v2_ingress_binding_pair_required/, 'principal without a binding is rejected before startup');
50
+ let standaloneIngressMode = '';
51
+ 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
+ runtimeFactoryV2 () {
75
+ return {};
76
+ },
77
+ createPlannerV2: async ()=>({
78
+ plannerFactory () {
79
+ return {
80
+ async next () {
81
+ return [];
82
+ }
83
+ };
84
+ },
85
+ async close () {}
86
+ }),
87
+ async startServer (options) {
88
+ standaloneIngressMode = options.v2?.ingressMode ?? '';
89
+ return {
90
+ server: {},
91
+ port: 43122,
92
+ async close () {}
93
+ };
94
+ }
95
+ });
96
+ assert.equal(standalone.port, 43122);
97
+ assert.equal(standaloneIngressMode, 'bff-bound-turn-only', 'standalone production startup uses the explicit bound-turn-only mode');
98
+ await standalone.close();
99
+ rmSync(standaloneStateRoot, {
100
+ recursive: true,
101
+ force: true
102
+ });
103
+ const closeOrder = [];
104
+ let plannerEnv;
105
+ let plannerV2Env;
106
+ let serverApiKey = '';
107
+ let serverArtifactId = '';
108
+ const trustedEnv = {
109
+ ...env,
110
+ GOTRY_BOOKING_COPILOT_V2_INGRESS_MODE: 'bff-ingress-binding'
111
+ };
112
+ const fakeLedger = {
113
+ close () {
114
+ closeOrder.push('ledger');
115
+ }
116
+ };
117
+ const trustedBinding = {
118
+ bind: ()=>({
119
+ taskId: 'startup-task',
120
+ turnId: 'startup-turn',
121
+ contextRef: 'startup-context',
122
+ surface: 'tenant',
123
+ allowedActions: [
124
+ ...BOOKING_READ_ACTION_KINDS_V2
125
+ ]
126
+ })
127
+ };
128
+ const started = await startBookingCopilotFromEnvironment(trustedEnv, {
129
+ ensureLedger () {
130
+ return fakeLedger;
131
+ },
132
+ runtimeFactory () {
133
+ return {};
134
+ },
135
+ async createPlanner (options) {
136
+ plannerEnv = options.env;
137
+ return {
138
+ plannerFactory () {
139
+ return {
140
+ async next () {
141
+ return [];
142
+ }
143
+ };
144
+ },
145
+ async close () {
146
+ closeOrder.push('planner');
147
+ }
148
+ };
149
+ },
150
+ runtimeFactoryV2 () {
151
+ return {};
152
+ },
153
+ async createPlannerV2 (options) {
154
+ plannerV2Env = options.env;
155
+ return {
156
+ plannerFactory () {
157
+ return {
158
+ async next () {
159
+ return [];
160
+ }
161
+ };
162
+ },
163
+ async close () {
164
+ closeOrder.push('planner-v2');
165
+ }
166
+ };
167
+ },
168
+ ingressBinding: trustedBinding,
169
+ principal: {
170
+ subject: 'startup-bff',
171
+ scope: 'booking:read'
172
+ },
173
+ async startServer (options) {
174
+ serverApiKey = options.apiKey;
175
+ serverArtifactId = options.artifactId ?? '';
176
+ assert.equal(options.v2?.ingressMode, 'bff-ingress-binding');
177
+ return {
178
+ server: {},
179
+ port: 43123,
180
+ async close () {
181
+ closeOrder.push('server');
182
+ }
183
+ };
184
+ }
185
+ });
186
+ assert.equal(started.port, 43123);
187
+ assert.equal(serverApiKey, 'bff-only-key', 'BFF deployment key terminates at the HTTP server');
188
+ assert.equal(serverArtifactId, env.GOTRY_BOOKING_COPILOT_ARTIFACT_ID);
189
+ assert.equal(plannerEnv?.DEEPSEEK_API_KEY, 'model-only-key');
190
+ assert.equal(plannerEnv?.GOTRY_BOOKING_COPILOT_API_KEY, undefined);
191
+ assert.equal(plannerEnv?.PORTAL_TOKEN, undefined);
192
+ assert.equal(plannerEnv?.HOTELBYTE_TOKEN, undefined);
193
+ assert.equal(plannerV2Env?.DEEPSEEK_API_KEY, 'model-only-key');
194
+ await started.close();
195
+ assert.deepEqual(closeOrder, [
196
+ 'server',
197
+ 'planner',
198
+ 'planner-v2',
199
+ 'ledger'
200
+ ]);
201
+ rmSync(stateRoot, {
202
+ recursive: true,
203
+ force: true
204
+ });
205
+ const v2StateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-v2-'));
206
+ let v2Passed = false;
207
+ let v2PlannerEnv;
208
+ const v2CloseOrder = [];
209
+ const v2Started = await startBookingCopilotFromEnvironment({
210
+ ...trustedEnv,
211
+ GOTRY_BOOKING_COPILOT_STATE_ROOT: v2StateRoot
212
+ }, {
213
+ ensureLedger () {
214
+ return {
215
+ close () {
216
+ v2CloseOrder.push('ledger');
217
+ }
218
+ };
219
+ },
220
+ runtimeFactory () {
221
+ return {};
222
+ },
223
+ createPlanner: async ()=>({
224
+ plannerFactory () {
225
+ return {
226
+ async next () {
227
+ return [];
228
+ }
229
+ };
230
+ },
231
+ async close () {}
232
+ }),
233
+ runtimeFactoryV2 () {
234
+ return {};
235
+ },
236
+ async createPlannerV2 (options) {
237
+ v2PlannerEnv = options.env;
238
+ return {
239
+ plannerFactory () {
240
+ return {
241
+ async next () {
242
+ return [];
243
+ }
244
+ };
245
+ },
246
+ async close () {
247
+ v2CloseOrder.push('planner-v2');
248
+ }
249
+ };
250
+ },
251
+ ingressBinding: trustedBinding,
252
+ principal: {
253
+ subject: 'startup-bff',
254
+ scope: 'booking:read'
255
+ },
256
+ async startServer (options) {
257
+ v2Passed = Boolean(options.v2 && options.runtime && options.plannerFactory);
258
+ return {
259
+ server: {},
260
+ port: 43124,
261
+ async close () {
262
+ v2CloseOrder.push('server');
263
+ }
264
+ };
265
+ }
266
+ });
267
+ assert.equal(v2Started.port, 43124);
268
+ assert.equal(v2Passed, true, 'v2 startup constructs and passes the real v2 adapter seam');
269
+ assert.equal(v2PlannerEnv?.DEEPSEEK_API_KEY, 'model-only-key');
270
+ await v2Started.close();
271
+ assert.deepEqual(v2CloseOrder, [
272
+ 'server',
273
+ 'planner-v2',
274
+ 'ledger'
275
+ ]);
276
+ rmSync(v2StateRoot, {
277
+ recursive: true,
278
+ force: true
279
+ });
280
+ console.log('BOOKING COPILOT STARTUP PROOF: env split/server+planner+ledger lifecycle OK');
281
+
282
+
283
+ //# sourceURL=ts/scripts/booking-copilot-startup-proof-tests.ts