@danceiny/gotry 0.0.1-rc.17 → 0.0.1-rc.19

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 (137) hide show
  1. package/README.md +185 -148
  2. package/README.zh-CN.md +195 -154
  3. package/bin/gotry-bootstrap.js +246 -26
  4. package/bin/gotry-inner.js +68 -38
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +15 -5
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/channel-health.js +103 -0
  11. package/dist/capabilities/channel-registry.js +213 -0
  12. package/dist/capabilities/doctor.js +285 -0
  13. package/dist/capabilities/effect.js +178 -5
  14. package/dist/capabilities/flyai.js +13 -0
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  16. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  17. package/dist/capabilities/session/extension-bridge.js +14 -1
  18. package/dist/capabilities/session/extension-channel.js +4 -2
  19. package/dist/capabilities/session/extension-distribution.js +1 -1
  20. package/dist/capabilities/session-search.js +311 -0
  21. package/dist/capabilities/visa-policy.js +126 -0
  22. package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
  23. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  24. package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
  25. package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
  26. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  27. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  28. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  29. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  30. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  31. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  32. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  33. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  34. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  35. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  36. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  37. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  38. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  39. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  40. package/dist/scripts/bootstrap-tests.js +115 -44
  41. package/dist/scripts/build-metrics-report.js +339 -0
  42. package/dist/scripts/channel-probe-tests.js +148 -0
  43. package/dist/scripts/channel-probe.js +252 -0
  44. package/dist/scripts/channel-registry-tests.js +262 -0
  45. package/dist/scripts/doctor-tests.js +121 -0
  46. package/dist/scripts/effect-tests.js +274 -1
  47. package/dist/scripts/extension-tests.js +129 -11
  48. package/dist/scripts/fact-gate-tests.js +91 -2
  49. package/dist/scripts/flyai-tests.js +17 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  51. package/dist/scripts/metrics-report-tests.js +288 -0
  52. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  53. package/dist/scripts/session-tests.js +401 -1
  54. package/dist/scripts/smoke.js +100 -83
  55. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  56. package/dist/scripts/turn-handoff-collect.js +189 -0
  57. package/dist/scripts/turn-policy-tests.js +56 -0
  58. package/dist/scripts/typed-contract-canary.js +261 -0
  59. package/dist/scripts/visa-policy-tests.js +75 -0
  60. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  61. package/dist/src/artifact-gate.js +91 -3
  62. package/dist/src/benchmark-agent-conformance.js +7 -4
  63. package/dist/src/benchmark-environment-bridge.js +68 -7
  64. package/dist/src/bookable-facts.js +62 -3
  65. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  66. package/dist/src/booking-surface/canonical-schema.js +8 -22
  67. package/dist/src/booking-surface/contracts.js +70 -19
  68. package/dist/src/booking-surface/dsh-planner.js +310 -177
  69. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  70. package/dist/src/booking-surface/index.js +1 -5
  71. package/dist/src/booking-surface/profile.js +1 -1
  72. package/dist/src/booking-surface/runtime.js +1682 -247
  73. package/dist/src/booking-surface/server.js +371 -185
  74. package/dist/src/booking-surface/startup.js +17 -27
  75. package/dist/src/booking-surface/validation.js +270 -760
  76. package/dist/src/index.js +735 -149
  77. package/dist/src/turn-deadline.js +292 -0
  78. package/dist/src/turn-policy.js +146 -0
  79. package/dist/src/wish-pool.js +5 -0
  80. package/extension/background.js +23 -4
  81. package/extension/content-main.js +47 -9
  82. package/extension/manifest.json +27 -9
  83. package/package.json +6 -26
  84. package/schemas/booking.surface.schema.json +2593 -0
  85. package/ts/capabilities/agent-reach.ts +4 -4
  86. package/ts/capabilities/flyai.ts +21 -3
  87. package/ts/capabilities/session/action-cache.ts +1 -1
  88. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  89. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  90. package/ts/capabilities/session/extension-bridge.ts +32 -2
  91. package/ts/capabilities/session/extension-channel.ts +9 -8
  92. package/ts/capabilities/session/extension-distribution.ts +3 -1
  93. package/ts/capabilities/session/health-watch.ts +1 -1
  94. package/ts/capabilities/session/read-guard.ts +1 -1
  95. package/ts/capabilities/session/wizard.ts +1 -1
  96. package/ts/capabilities/session-search.ts +323 -1
  97. package/ts/package.json +1 -0
  98. package/ts/scripts/turn-handoff-collect.ts +178 -0
  99. package/ts/src/artifact-gate.ts +82 -2
  100. package/ts/src/benchmark-agent-conformance.ts +14 -6
  101. package/ts/src/benchmark-environment-bridge.ts +29 -10
  102. package/ts/src/bookable-facts.ts +109 -5
  103. package/ts/src/booking-saga.ts +1 -1
  104. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  105. package/ts/src/booking-surface/canonical-schema.js +8 -22
  106. package/ts/src/booking-surface/contracts.ts +278 -239
  107. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  108. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  109. package/ts/src/booking-surface/index.ts +1 -5
  110. package/ts/src/booking-surface/profile.ts +11 -11
  111. package/ts/src/booking-surface/runtime.ts +1351 -351
  112. package/ts/src/booking-surface/server.ts +282 -175
  113. package/ts/src/booking-surface/startup.ts +36 -48
  114. package/ts/src/booking-surface/validation.ts +194 -442
  115. package/ts/src/contracts.ts +1 -1
  116. package/ts/src/index.ts +489 -171
  117. package/ts/src/loop.ts +1 -1
  118. package/ts/src/state-ledger.ts +1 -1
  119. package/ts/src/turn-deadline.ts +361 -0
  120. package/ts/src/turn-policy.ts +154 -0
  121. package/ts/src/wish-pool.ts +17 -4
  122. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  123. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  124. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  125. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  126. package/dist/src/booking-surface/contracts-v2.js +0 -89
  127. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  128. package/dist/src/booking-surface/server-v2.js +0 -334
  129. package/dist/src/booking-surface/validation-v2.js +0 -319
  130. package/dist/src/tool-budget.js +0 -136
  131. package/schemas/booking.surface.v1.schema.json +0 -927
  132. package/schemas/booking.surface.v2.schema.json +0 -61
  133. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  134. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  135. package/ts/src/booking-surface/server-v2.ts +0 -247
  136. package/ts/src/booking-surface/validation-v2.ts +0 -205
  137. package/ts/src/tool-budget.ts +0 -165
@@ -1,1174 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { readFileSync } from 'node:fs';
3
- import { createHash } from 'node:crypto';
4
- import { BOOKING_READ_ACTION_KINDS_V2, BOOKING_V2_GAP_CODES, BOOKING_V2_BLOCKER_CODES, BOOKING_SURFACE_SCHEMA_V2_SHA256, bookingSurfaceAllowedActionsV2 } from '../src/booking-surface/contracts-v2.js';
5
- import { validateBookingSurfaceV2, validateBookingReadActionV2, validateBookingSurfaceEventV2, validateCriterionBlockerV2, validateRelaxationApprovalV2, validateApprovalAgainstBlocker } from '../src/booking-surface/validation-v2.js';
6
- let positive = 0;
7
- let negative = 0;
8
- function accept(label, value) {
9
- assert.equal(validateBookingSurfaceV2(value).ok, true, label);
10
- positive++;
11
- }
12
- function reject(label, value) {
13
- assert.equal(validateBookingSurfaceV2(value).ok, false, label);
14
- negative++;
15
- }
16
- assert.deepEqual(bookingSurfaceAllowedActionsV2('storefront'), [
17
- 'search.patch',
18
- 'search.run',
19
- 'results.view.patch',
20
- 'hotel.focus'
21
- ], 'storefront UAT availability observation is least privilege');
22
- assert.deepEqual(bookingSurfaceAllowedActionsV2('payment_link'), [
23
- 'search.patch',
24
- 'search.run',
25
- 'results.view.patch',
26
- 'hotel.focus',
27
- 'hotel.select'
28
- ], 'payment_link UAT availability observation is least privilege');
29
- assert.ok(!bookingSurfaceAllowedActionsV2('storefront').includes('offers.query'), 'storefront availability cannot expand into offers');
30
- assert.ok(!bookingSurfaceAllowedActionsV2('payment_link').includes('checkout.prepare'), 'payment_link availability cannot expand into checkout');
31
- const hash = 'a'.repeat(64);
32
- const workspace = {
33
- schemaVersion: 'booking.surface.v2',
34
- contextRef: 'ctx-1',
35
- surface: 'tenant',
36
- revision: 0,
37
- locale: 'zh-CN',
38
- currency: 'AED',
39
- searchDraft: {},
40
- results: {
41
- status: 'idle'
42
- },
43
- visibleHotels: [],
44
- loadedOffers: [],
45
- shortlistedOfferRefs: [],
46
- capabilities: {
47
- surface: 'tenant',
48
- allowedActions: [
49
- ...BOOKING_READ_ACTION_KINDS_V2
50
- ]
51
- }
52
- };
53
- const ingressWorkspace = {
54
- ...workspace
55
- };
56
- delete ingressWorkspace.contextRef;
57
- delete ingressWorkspace.surface;
58
- delete ingressWorkspace.capabilities;
59
- const inputs = {
60
- 'search.patch': {
61
- patch: {
62
- destination: {
63
- query: 'Dubai'
64
- }
65
- }
66
- },
67
- 'search.run': {},
68
- 'results.view.patch': {
69
- patch: {
70
- sort: 'recommended'
71
- }
72
- },
73
- 'hotel.focus': {
74
- hotelRef: 'hotel-1'
75
- },
76
- 'hotel.select': {
77
- hotelRef: 'hotel-1'
78
- },
79
- 'offers.query': {
80
- hotelRefs: [
81
- 'hotel-1'
82
- ],
83
- criteria: {}
84
- },
85
- 'offers.view.patch': {
86
- hotelRef: 'hotel-1',
87
- criteria: {}
88
- },
89
- 'offers.compare': {
90
- offerRefs: [
91
- 'offer-1'
92
- ],
93
- requestedCount: 1
94
- },
95
- 'offer.select': {
96
- offerRef: 'offer-1',
97
- offerVersionRef: 'offer-1:v1'
98
- },
99
- 'offer.check': {
100
- offerRef: 'offer-1',
101
- offerVersionRef: 'offer-1:v1'
102
- },
103
- 'checkout.prepare': {
104
- offerRef: 'offer-1',
105
- offerVersionRef: 'offer-1:v1',
106
- verifiedOfferRef: 'verified-1'
107
- },
108
- 'order.observe': {
109
- orderRef: 'order-1'
110
- }
111
- };
112
- const actions = Object.fromEntries(BOOKING_READ_ACTION_KINDS_V2.map((kind)=>[
113
- kind,
114
- {
115
- schemaVersion: 'booking.surface.v2',
116
- kind,
117
- actionId: `a-${kind.replaceAll('.', '-')}`,
118
- contextRef: 'ctx-1',
119
- expectedRevision: 0,
120
- reason: 'typed request',
121
- factRefs: [],
122
- input: inputs[kind]
123
- }
124
- ]));
125
- const approvalRef = {
126
- approvalId: 'ap-1',
127
- blockerId: 'b-1',
128
- contextRef: 'ctx-1',
129
- sourceTurnId: 'turn-1',
130
- presentationRequestKey: 'approval:task-1:turn-1:a-search-run:' + hash,
131
- sourceActionId: 'a-search-run',
132
- targetActionId: 'a-search-run',
133
- sourceRevision: 0,
134
- targetActionKind: 'search.run',
135
- to: 'prefer',
136
- expiresAt: '2099-01-01T00:00:00.000Z',
137
- nonce: 'nonce-1',
138
- sourceReceiptDigest: hash,
139
- scope: 'offer',
140
- code: 'hotel_rates_failed',
141
- criterionPath: 'offers.totalPriceMax',
142
- valueDigest: hash
143
- };
144
- accept('valid relaxation approval ref action', {
145
- ...actions['search.run'],
146
- relaxationApprovalRef: approvalRef
147
- });
148
- reject('approval context replay', {
149
- ...actions['search.run'],
150
- relaxationApprovalRef: {
151
- ...approvalRef,
152
- contextRef: 'ctx-other'
153
- }
154
- });
155
- reject('approval action replay', {
156
- ...actions['search.run'],
157
- relaxationApprovalRef: {
158
- ...approvalRef,
159
- targetActionId: 'a-other'
160
- }
161
- });
162
- reject('approval future revision replay', {
163
- ...actions['search.run'],
164
- relaxationApprovalRef: {
165
- ...approvalRef,
166
- sourceRevision: 1
167
- }
168
- });
169
- reject('approval old revision replay', {
170
- ...actions['search.run'],
171
- expectedRevision: 1,
172
- relaxationApprovalRef: {
173
- ...approvalRef,
174
- sourceRevision: 0
175
- }
176
- });
177
- reject('approval target kind replay', {
178
- ...actions['search.run'],
179
- relaxationApprovalRef: {
180
- ...approvalRef,
181
- targetActionKind: 'offer.check'
182
- }
183
- });
184
- reject('approval bad nonce', {
185
- ...actions['search.run'],
186
- relaxationApprovalRef: {
187
- ...approvalRef,
188
- nonce: 'Bearer token'
189
- }
190
- });
191
- reject('approval bad expiry', {
192
- ...actions['search.run'],
193
- relaxationApprovalRef: {
194
- ...approvalRef,
195
- expiresAt: 'not-a-date'
196
- }
197
- });
198
- const openInput = (kind)=>{
199
- const input = inputs[kind];
200
- return input && typeof input === 'object' && !Array.isArray(input) ? {
201
- ...input,
202
- open: true
203
- } : {
204
- open: true
205
- };
206
- };
207
- for (const kind of BOOKING_READ_ACTION_KINDS_V2){
208
- accept(`valid action ${kind}`, actions[kind]);
209
- reject(`missing input ${kind}`, {
210
- ...actions[kind],
211
- input: undefined
212
- });
213
- reject(`open input ${kind}`, {
214
- ...actions[kind],
215
- input: openInput(kind)
216
- });
217
- }
218
- reject('offer.select missing version', {
219
- ...actions['offer.select'],
220
- input: {
221
- offerRef: 'offer-1'
222
- }
223
- });
224
- reject('offer.check missing version', {
225
- ...actions['offer.check'],
226
- input: {
227
- offerRef: 'offer-1'
228
- }
229
- });
230
- reject('checkout.prepare missing version', {
231
- ...actions['checkout.prepare'],
232
- input: {
233
- offerRef: 'offer-1',
234
- verifiedOfferRef: 'verified-1'
235
- }
236
- });
237
- const versionedWorkspace = {
238
- ...workspace,
239
- visibleHotels: [
240
- {
241
- hotelRef: 'hotel-1',
242
- name: 'Hotel 1',
243
- factRefs: [
244
- 'hotel-fact-1'
245
- ]
246
- }
247
- ],
248
- loadedOffers: [
249
- {
250
- offerRef: 'offer-1',
251
- offerVersionRef: 'offer-1:v1',
252
- hotelRef: 'hotel-1',
253
- evidenceLevel: 'rate_loaded',
254
- factRefs: [
255
- 'offer-fact-1'
256
- ]
257
- }
258
- ],
259
- selectedOfferRef: 'offer-1',
260
- verifiedOffer: {
261
- offerRef: 'offer-1',
262
- offerVersionRef: 'offer-1:v1',
263
- verifiedOfferRef: 'verified-1',
264
- expiresAt: '2030-02-28T23:59:59+04:00'
265
- }
266
- };
267
- const versionedTurn = {
268
- schemaVersion: 'booking.surface.v2',
269
- kind: 'user.turn',
270
- taskId: 'versioned-task',
271
- turnId: 'versioned-turn',
272
- workspace: versionedWorkspace,
273
- request: {
274
- text: 'find hotels'
275
- }
276
- };
277
- accept('versioned loaded offer and verified tuple', versionedTurn);
278
- reject('verified offer invalid calendar expiry', {
279
- ...versionedTurn,
280
- workspace: {
281
- ...versionedWorkspace,
282
- verifiedOffer: {
283
- ...versionedWorkspace.verifiedOffer,
284
- expiresAt: '2026-02-30T00:00:00.000Z'
285
- }
286
- }
287
- });
288
- reject('verified offer malformed expiry', {
289
- ...versionedTurn,
290
- workspace: {
291
- ...versionedWorkspace,
292
- verifiedOffer: {
293
- ...versionedWorkspace.verifiedOffer,
294
- expiresAt: 'tomorrow'
295
- }
296
- }
297
- });
298
- reject('verified offer missing expiry', {
299
- ...versionedTurn,
300
- workspace: {
301
- ...versionedWorkspace,
302
- verifiedOffer: {
303
- ...versionedWorkspace.verifiedOffer,
304
- expiresAt: undefined
305
- }
306
- }
307
- });
308
- reject('verified offer mismatched logical tuple', {
309
- ...versionedTurn,
310
- workspace: {
311
- ...versionedWorkspace,
312
- verifiedOffer: {
313
- ...versionedWorkspace.verifiedOffer,
314
- offerRef: 'offer-other',
315
- offerVersionRef: 'offer-other:v1'
316
- }
317
- }
318
- });
319
- reject('verified offer mismatched version tuple', {
320
- ...versionedTurn,
321
- workspace: {
322
- ...versionedWorkspace,
323
- verifiedOffer: {
324
- ...versionedWorkspace.verifiedOffer,
325
- offerVersionRef: 'offer-1:v2'
326
- }
327
- }
328
- });
329
- reject('workspace duplicate logical offer refs', {
330
- ...versionedTurn,
331
- workspace: {
332
- ...versionedWorkspace,
333
- loadedOffers: [
334
- ...versionedWorkspace.loadedOffers,
335
- {
336
- ...versionedWorkspace.loadedOffers[0]
337
- }
338
- ]
339
- }
340
- });
341
- reject('workspace duplicate offer version refs', {
342
- ...versionedTurn,
343
- workspace: {
344
- ...versionedWorkspace,
345
- loadedOffers: [
346
- ...versionedWorkspace.loadedOffers,
347
- {
348
- offerRef: 'offer-2',
349
- offerVersionRef: 'offer-1:v1',
350
- hotelRef: 'hotel-1',
351
- evidenceLevel: 'rate_loaded',
352
- factRefs: []
353
- }
354
- ]
355
- }
356
- });
357
- reject('Book', {
358
- ...actions['search.run'],
359
- kind: 'book'
360
- });
361
- reject('root minimum', {
362
- schemaVersion: 'booking.surface.v2'
363
- });
364
- reject('workspace nested extra', {
365
- ...workspace,
366
- results: {
367
- status: 'idle',
368
- open: true
369
- }
370
- });
371
- reject('workspace capability Book', {
372
- ...workspace,
373
- capabilities: {
374
- ...workspace.capabilities,
375
- allowedActions: [
376
- 'book'
377
- ]
378
- }
379
- });
380
- for (const field of [
381
- 'schemaVersion',
382
- 'kind',
383
- 'actionId',
384
- 'contextRef',
385
- 'expectedRevision'
386
- ])reject(`action missing ${field}`, Object.fromEntries(Object.entries(actions['search.run']).filter(([key])=>key !== field)));
387
- const blocker = {
388
- blockerId: 'b-1',
389
- sourceActionId: 'a-offers-query',
390
- sourceReceiptDigest: hash,
391
- scope: 'offer',
392
- code: 'hotel_rates_failed',
393
- criterionPath: 'offers.totalPriceMax',
394
- strength: 'must',
395
- valueDigest: hash,
396
- evidence: {
397
- factRefs: [
398
- 'fact-1'
399
- ],
400
- gapCodes: [
401
- 'hotel_rates_failed'
402
- ],
403
- requested: 2,
404
- actual: 1
405
- }
406
- };
407
- const approval = {
408
- taskId: 'task-1',
409
- contextRef: 'ctx-1',
410
- sourceTurnId: 'turn-1',
411
- presentationRequestKey: 'approval:task-1:turn-1:a-offers-query:' + hash,
412
- optionDigest: hash,
413
- approvalId: 'ap-1',
414
- deliveryNonce: 'delivery-nonce-1',
415
- blockerId: 'b-1',
416
- sourceActionId: blocker.sourceActionId,
417
- sourceReceiptDigest: hash,
418
- scope: 'offer',
419
- code: blocker.code,
420
- criterionPath: blocker.criterionPath,
421
- valueDigest: hash,
422
- from: 'must',
423
- to: 'prefer',
424
- approved: true
425
- };
426
- assert.equal(validateCriterionBlockerV2(blocker).ok, true);
427
- positive++;
428
- assert.equal(validateRelaxationApprovalV2(approval).ok, true);
429
- positive++;
430
- assert.equal(validateApprovalAgainstBlocker(approval, blocker).ok, true);
431
- positive++;
432
- for (const field of [
433
- 'sourceReceiptDigest',
434
- 'valueDigest'
435
- ]){
436
- assert.equal(validateCriterionBlockerV2({
437
- ...blocker,
438
- [field]: 'bad'
439
- }).ok, false);
440
- negative++;
441
- }
442
- for (const field of [
443
- 'code',
444
- 'scope',
445
- 'criterionPath'
446
- ]){
447
- assert.equal(validateCriterionBlockerV2({
448
- ...blocker,
449
- [field]: field === 'criterionPath' ? 'open.path' : 'dynamic'
450
- }).ok, false);
451
- negative++;
452
- }
453
- assert.equal(validateCriterionBlockerV2({
454
- ...blocker,
455
- evidence: {
456
- ...blocker.evidence,
457
- factRefs: [
458
- 'fact-1',
459
- 'fact-1'
460
- ]
461
- }
462
- }).ok, false);
463
- negative++;
464
- assert.equal(validateCriterionBlockerV2({
465
- ...blocker,
466
- evidence: {
467
- ...blocker.evidence,
468
- open: true
469
- }
470
- }).ok, false);
471
- negative++;
472
- const receipt = {
473
- schemaVersion: 'booking.surface.v2',
474
- kind: 'action.receipt',
475
- actionId: 'a-search-run',
476
- contextRef: 'ctx-1',
477
- status: 'partial',
478
- revision: 1,
479
- observation: {
480
- kind: 'gap',
481
- code: 'search_failed',
482
- factRefs: [
483
- 'fact-1'
484
- ]
485
- },
486
- resultContract: {
487
- outcome: 'partial',
488
- hardCriteriaMet: false,
489
- factRefs: [
490
- 'fact-1'
491
- ],
492
- gapCodes: [
493
- ...BOOKING_V2_GAP_CODES
494
- ],
495
- blockers: [
496
- blocker
497
- ],
498
- relaxationsApplied: []
499
- }
500
- };
501
- accept('valid receipt all gap codes', receipt);
502
- reject('bad status', {
503
- ...receipt,
504
- status: 'unknown'
505
- });
506
- reject('bad revision', {
507
- ...receipt,
508
- revision: -1
509
- });
510
- reject('bad outcome', {
511
- ...receipt,
512
- resultContract: {
513
- ...receipt.resultContract,
514
- outcome: 'bad'
515
- }
516
- });
517
- reject('dynamic gap', {
518
- ...receipt,
519
- resultContract: {
520
- ...receipt.resultContract,
521
- gapCodes: [
522
- 'dynamic'
523
- ]
524
- }
525
- });
526
- reject('receipt observation extra', {
527
- ...receipt,
528
- observation: {
529
- ...receipt.observation,
530
- open: true
531
- }
532
- });
533
- for (const code of BOOKING_V2_GAP_CODES){
534
- accept(`valid declared gap ${code}`, {
535
- ...receipt,
536
- resultContract: {
537
- ...receipt.resultContract,
538
- gapCodes: [
539
- code
540
- ]
541
- }
542
- });
543
- reject(`dynamic gap ${code}`, {
544
- ...receipt,
545
- resultContract: {
546
- ...receipt.resultContract,
547
- gapCodes: [
548
- `${code}:dynamic`
549
- ]
550
- }
551
- });
552
- }
553
- const observations = [
554
- {
555
- kind: 'search.state',
556
- searchSessionRef: 'session-1',
557
- resultCount: 1
558
- },
559
- {
560
- kind: 'results.state',
561
- matchedHotelRefs: [
562
- 'hotel-1'
563
- ],
564
- visibleCount: 1
565
- },
566
- {
567
- kind: 'hotel.focus',
568
- hotelRef: 'hotel-1'
569
- },
570
- {
571
- kind: 'hotel.selection',
572
- hotelRef: 'hotel-1'
573
- },
574
- {
575
- kind: 'offers.state',
576
- hotelRefs: [
577
- 'hotel-1'
578
- ],
579
- offerRefs: [
580
- 'offer-1'
581
- ],
582
- loadedHotelCount: 1
583
- },
584
- {
585
- kind: 'offer.selection',
586
- offerRef: 'offer-1',
587
- offerVersionRef: 'offer-1:v1'
588
- },
589
- {
590
- kind: 'offer.availability',
591
- offerRef: 'offer-1',
592
- checkedOfferVersionRef: 'offer-1:v1',
593
- currentOfferVersionRef: 'offer-1:v1',
594
- available: true,
595
- changedFactRefs: []
596
- },
597
- {
598
- kind: 'checkout.handoff',
599
- offerRef: 'offer-1',
600
- offerVersionRef: 'offer-1:v1',
601
- verifiedOfferRef: 'verified-1',
602
- handoffRef: 'handoff-1'
603
- },
604
- {
605
- kind: 'order.state',
606
- orderRef: 'order-1',
607
- state: 'pending'
608
- },
609
- {
610
- kind: 'gap',
611
- code: 'search_failed',
612
- factRefs: [
613
- 'fact-1'
614
- ]
615
- }
616
- ];
617
- for (const observation of observations){
618
- accept(`valid observation ${observation.kind}`, {
619
- ...receipt,
620
- observation
621
- });
622
- reject(`observation extra ${observation.kind}`, {
623
- ...receipt,
624
- observation: {
625
- ...observation,
626
- extra: true
627
- }
628
- });
629
- }
630
- for (const observation of observations.filter((item)=>[
631
- 'search.state',
632
- 'results.state',
633
- 'offers.state',
634
- 'offer.availability',
635
- 'order.state'
636
- ].includes(item.kind))){
637
- reject(`observation dynamic gap ${observation.kind}`, {
638
- ...receipt,
639
- observation: {
640
- ...observation,
641
- gapCodes: [
642
- 'dynamic'
643
- ]
644
- }
645
- });
646
- }
647
- reject('gap observation dynamic code', {
648
- ...receipt,
649
- observation: {
650
- kind: 'gap',
651
- code: 'dynamic',
652
- factRefs: []
653
- }
654
- });
655
- const turn = {
656
- schemaVersion: 'booking.surface.v2',
657
- kind: 'user.turn',
658
- taskId: 'task-1',
659
- turnId: 'turn-1',
660
- workspace,
661
- request: {
662
- text: 'find hotels'
663
- }
664
- };
665
- const ingress = {
666
- schemaVersion: 'booking.surface.v2',
667
- kind: 'user.turn.ingress',
668
- requestKey: 'request-1',
669
- taskHandle: 'opaque-task-handle-1',
670
- surfaceHint: 'tenant',
671
- workspace: ingressWorkspace,
672
- request: {
673
- text: 'find hotels'
674
- }
675
- };
676
- const continuation = {
677
- schemaVersion: 'booking.surface.v2',
678
- kind: 'action.receipt.continuation',
679
- taskId: 'task-1',
680
- workspace,
681
- receipt
682
- };
683
- accept('valid user turn', turn);
684
- accept('valid ingress', ingress);
685
- accept('valid continuation', continuation);
686
- reject('empty text', {
687
- ...turn,
688
- request: {
689
- text: ''
690
- }
691
- });
692
- reject('multiple approvals', {
693
- ...turn,
694
- request: {
695
- text: 'x',
696
- approval: [
697
- approval,
698
- approval
699
- ]
700
- }
701
- });
702
- accept('text only remains non-approval', {
703
- ...turn,
704
- request: {
705
- text: 'please relax must'
706
- }
707
- });
708
- const selectedIngressWorkspace = {
709
- ...ingressWorkspace,
710
- visibleHotels: [
711
- {
712
- hotelRef: 'hotel-1',
713
- name: 'Hotel 1',
714
- factRefs: [
715
- 'hotel-fact-1'
716
- ]
717
- }
718
- ],
719
- loadedOffers: [
720
- {
721
- offerRef: 'offer-1',
722
- offerVersionRef: 'offer-1:v1',
723
- hotelRef: 'hotel-1',
724
- evidenceLevel: 'rate_loaded',
725
- factRefs: [
726
- 'offer-fact-1'
727
- ]
728
- }
729
- ],
730
- focusedHotelRef: 'hotel-1',
731
- selectedOfferRef: 'offer-1'
732
- };
733
- accept('ingress preserves visible focus and loaded selection', {
734
- ...ingress,
735
- workspace: selectedIngressWorkspace
736
- });
737
- reject('ingress rejects invisible focus', {
738
- ...ingress,
739
- workspace: {
740
- ...selectedIngressWorkspace,
741
- focusedHotelRef: 'hotel-other'
742
- }
743
- });
744
- reject('ingress rejects unloaded selection', {
745
- ...ingress,
746
- workspace: {
747
- ...selectedIngressWorkspace,
748
- selectedOfferRef: 'offer-other'
749
- }
750
- });
751
- reject('ingress rejects selection whose hotel is not visible', {
752
- ...ingress,
753
- workspace: {
754
- ...selectedIngressWorkspace,
755
- visibleHotels: [
756
- {
757
- hotelRef: 'hotel-other',
758
- name: 'Other',
759
- factRefs: []
760
- }
761
- ]
762
- }
763
- });
764
- reject('ingress never accepts verified authority', {
765
- ...ingress,
766
- workspace: {
767
- ...selectedIngressWorkspace,
768
- verifiedOfferRef: 'verified-offer-1'
769
- }
770
- });
771
- reject('missing user turn identity', {
772
- ...turn,
773
- turnId: undefined
774
- });
775
- reject('missing ingress request key', {
776
- ...ingress,
777
- requestKey: undefined
778
- });
779
- reject('unsafe user turn identity', {
780
- ...turn,
781
- turnId: 'Bearer token'
782
- });
783
- reject('unsafe ingress task handle', {
784
- ...ingress,
785
- taskHandle: 'user@example.com'
786
- });
787
- reject('missing user task identity', {
788
- ...turn,
789
- taskId: undefined
790
- });
791
- reject('browser task identity injection', {
792
- ...ingress,
793
- taskId: 'browser-task'
794
- });
795
- reject('browser turn identity injection', {
796
- ...ingress,
797
- turnId: 'browser-turn'
798
- });
799
- reject('browser context identity injection', {
800
- ...ingress,
801
- contextRef: null
802
- });
803
- reject('browser verified authority injection', {
804
- ...ingress,
805
- workspace: {
806
- ...ingressWorkspace,
807
- verifiedOfferRef: 'verified-offer-1'
808
- }
809
- });
810
- assert.equal(validateCriterionBlockerV2({
811
- ...blocker,
812
- evidence: {
813
- factRefs: [],
814
- gapCodes: []
815
- }
816
- }).ok, false);
817
- negative++;
818
- assert.equal(validateApprovalAgainstBlocker({
819
- ...approval,
820
- blockerId: 'wrong'
821
- }, blocker).ok, false);
822
- negative++;
823
- const continuationBlocked = {
824
- ...continuation,
825
- receipt: {
826
- ...receipt,
827
- resultContract: {
828
- ...receipt.resultContract,
829
- hardCriteriaMet: true
830
- }
831
- }
832
- };
833
- reject('continuation hard criteria with blocker', continuationBlocked);
834
- reject('formatted phone', {
835
- ...turn,
836
- request: {
837
- text: '+971 (50) 123 4567'
838
- }
839
- });
840
- reject('dotted phone', {
841
- ...turn,
842
- request: {
843
- text: '+971.50.123.4567'
844
- }
845
- });
846
- reject('slash phone', {
847
- ...turn,
848
- request: {
849
- text: 'call +971/50/123/4567'
850
- }
851
- });
852
- reject('comma phone', {
853
- ...turn,
854
- request: {
855
- text: 'call +971,50,123,4567'
856
- }
857
- });
858
- reject('unicode dash phone', {
859
- ...turn,
860
- request: {
861
- text: 'call +971—50—123—4567'
862
- }
863
- });
864
- reject('nbsp phone', {
865
- ...turn,
866
- request: {
867
- text: '+971\u00a050\u00a0123\u00a04567'
868
- }
869
- });
870
- reject('embedded phone', {
871
- ...turn,
872
- request: {
873
- text: 'call +971 50 123 4567 please'
874
- }
875
- });
876
- reject('payment card', {
877
- ...turn,
878
- request: {
879
- text: '4111 1111 1111 1111'
880
- }
881
- });
882
- reject('supplier cost', {
883
- ...turn,
884
- request: {
885
- text: 'supplier net cost AED 42'
886
- }
887
- });
888
- reject('supplier cost colon', {
889
- ...turn,
890
- request: {
891
- text: 'supplier net cost: AED 42'
892
- }
893
- });
894
- reject('supplier possessive cost', {
895
- ...turn,
896
- request: {
897
- text: "supplier's net cost AED 42"
898
- }
899
- });
900
- reject('supplier cost reversed currency', {
901
- ...turn,
902
- request: {
903
- text: 'supplier cost 42 AED'
904
- }
905
- });
906
- reject('supplier wholesale', {
907
- ...turn,
908
- request: {
909
- text: 'supplier wholesale rate AED 42'
910
- }
911
- });
912
- reject('supplier base bypass', {
913
- ...turn,
914
- request: {
915
- text: 'supplier base cost AED 42'
916
- }
917
- });
918
- reject('vendor cost', {
919
- ...turn,
920
- request: {
921
- text: 'vendor net cost AED 42'
922
- }
923
- });
924
- reject('vendor compact amount', {
925
- ...turn,
926
- request: {
927
- text: 'vendor price AED42'
928
- }
929
- });
930
- reject('internal supplier cost', {
931
- ...turn,
932
- request: {
933
- text: 'internal supplier cost AED 42'
934
- }
935
- });
936
- reject('internal reversed amount', {
937
- ...turn,
938
- request: {
939
- text: 'internal rate 42 USD'
940
- }
941
- });
942
- reject('contact email bypass', {
943
- ...turn,
944
- request: {
945
- text: 'contact user@hotelbyte.com'
946
- }
947
- });
948
- reject('quoted email local part', {
949
- ...turn,
950
- request: {
951
- text: 'contact "user"@hotelbyte.com'
952
- }
953
- });
954
- reject('unicode email local part', {
955
- ...turn,
956
- request: {
957
- text: '联系 用户@hotelbyte.com'
958
- }
959
- });
960
- reject('technical-looking real mailbox', {
961
- ...turn,
962
- request: {
963
- text: 'version@company.com'
964
- }
965
- });
966
- accept('benign trace identifier', {
967
- ...turn,
968
- request: {
969
- text: 'trace 0000000000000000'
970
- }
971
- });
972
- accept('benign sk-ills sentence', {
973
- ...turn,
974
- request: {
975
- text: 'sk-ills are useful'
976
- }
977
- });
978
- accept('benign version email', {
979
- ...turn,
980
- request: {
981
- text: 'version@release.example'
982
- }
983
- });
984
- accept('benign schema identifier', {
985
- ...turn,
986
- request: {
987
- text: 'schema@v2.example'
988
- }
989
- });
990
- accept('benign budget', {
991
- ...turn,
992
- request: {
993
- text: 'budget AED 1000'
994
- }
995
- });
996
- accept('benign total price', {
997
- ...turn,
998
- request: {
999
- text: 'total price under AED 1000'
1000
- }
1001
- });
1002
- const common = {
1003
- schemaVersion: 'booking.surface.v2',
1004
- eventId: 'e-1',
1005
- taskId: 'task-1',
1006
- contextRef: 'ctx-1',
1007
- sequence: 1,
1008
- emittedAt: '2026-08-30T12:00:00.000Z'
1009
- };
1010
- const questionEvent = {
1011
- ...common,
1012
- kind: 'question',
1013
- question: {
1014
- questionId: 'q-1',
1015
- prompt: 'Relax?',
1016
- missingFields: [],
1017
- type: 'relaxation_approval_required',
1018
- blocker,
1019
- approvalOptions: [
1020
- {
1021
- approval
1022
- }
1023
- ]
1024
- }
1025
- };
1026
- const events = [
1027
- {
1028
- ...common,
1029
- kind: 'status',
1030
- status: 'working'
1031
- },
1032
- questionEvent,
1033
- {
1034
- ...common,
1035
- kind: 'operation',
1036
- action: actions['search.run']
1037
- },
1038
- {
1039
- ...common,
1040
- kind: 'explanation',
1041
- explanation: {
1042
- text: 'x',
1043
- factRefs: []
1044
- }
1045
- },
1046
- {
1047
- ...common,
1048
- kind: 'terminal',
1049
- terminal: {
1050
- status: 'completed',
1051
- summary: 'x',
1052
- factRefs: []
1053
- }
1054
- },
1055
- {
1056
- ...common,
1057
- kind: 'error',
1058
- error: {
1059
- code: 'ERR',
1060
- message: 'x',
1061
- retryable: false
1062
- }
1063
- }
1064
- ];
1065
- assert.equal(validateBookingSurfaceEventV2({
1066
- ...questionEvent,
1067
- question: {
1068
- ...questionEvent.question,
1069
- approvalOptions: [
1070
- {
1071
- approval: {
1072
- ...approval,
1073
- blockerId: 'wrong'
1074
- }
1075
- }
1076
- ]
1077
- }
1078
- }).ok, false);
1079
- negative++;
1080
- for (const event of events)accept(`valid event ${event.kind}`, event);
1081
- assert.equal(validateBookingSurfaceEventV2({
1082
- ...events[5],
1083
- eventId: 'event-4242-4242-4242-4242'
1084
- }).ok, true);
1085
- positive++;
1086
- assert.equal(validateBookingSurfaceEventV2({
1087
- ...events[3],
1088
- explanation: {
1089
- text: 'card 4111 1111 1111 1111',
1090
- factRefs: []
1091
- }
1092
- }).ok, false);
1093
- negative++;
1094
- reject('event mixed payload', {
1095
- ...events[0],
1096
- question: {}
1097
- });
1098
- reject('event missing payload', {
1099
- ...events[1],
1100
- question: undefined
1101
- });
1102
- reject('event open nested', {
1103
- ...events[4],
1104
- terminal: {
1105
- status: 'completed',
1106
- summary: 'x',
1107
- factRefs: [],
1108
- open: true
1109
- }
1110
- });
1111
- reject('request key whitespace', {
1112
- ...ingress,
1113
- requestKey: 'request key'
1114
- });
1115
- reject('request key newline', {
1116
- ...ingress,
1117
- requestKey: 'request-1\nnext'
1118
- });
1119
- reject('request key path', {
1120
- ...ingress,
1121
- requestKey: 'request/path'
1122
- });
1123
- reject('request key email', {
1124
- ...ingress,
1125
- requestKey: 'user@example.com'
1126
- });
1127
- reject('request key secret-like', {
1128
- ...ingress,
1129
- requestKey: 'sk-live-abcdefghijkl'
1130
- });
1131
- reject('request key too short', {
1132
- ...ingress,
1133
- requestKey: 'r1'
1134
- });
1135
- accept('ISO date and currency', {
1136
- ...workspace,
1137
- searchDraft: {
1138
- stay: {
1139
- checkIn: '2026-09-01',
1140
- checkOut: '2026-09-03'
1141
- }
1142
- }
1143
- });
1144
- reject('email secret', {
1145
- ...turn,
1146
- request: {
1147
- text: 'user@example.com'
1148
- }
1149
- });
1150
- reject('Bearer secret', {
1151
- ...turn,
1152
- request: {
1153
- text: 'Bearer abc'
1154
- }
1155
- });
1156
- reject('sk secret', {
1157
- ...turn,
1158
- request: {
1159
- text: 'sk-live-abcdefghijkl'
1160
- }
1161
- });
1162
- reject('JWT secret', {
1163
- ...turn,
1164
- request: {
1165
- text: 'eyJabc.def.ghi'
1166
- }
1167
- });
1168
- accept('E164-like ordinary date safe', turn);
1169
- assert.equal(createHash('sha256').update(readFileSync(new URL('../../schemas/booking.surface.v2.schema.json', import.meta.url))).digest('hex'), BOOKING_SURFACE_SCHEMA_V2_SHA256);
1170
- console.log(`BOOKING SURFACE V2 CONTRACT PROOF: positive=${positive} negative=${negative}`);
1171
- assert.ok(positive >= 65 && negative >= 105);
1172
-
1173
-
1174
- //# sourceURL=ts/scripts/booking-surface-v2-contract-proof-tests.ts