@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.
- package/README.md +185 -148
- package/README.zh-CN.md +195 -154
- package/bin/gotry-bootstrap.js +246 -26
- package/bin/gotry-inner.js +68 -38
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +15 -5
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +285 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
- package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +115 -44
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +121 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +7 -4
- package/dist/src/benchmark-environment-bridge.js +68 -7
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +735 -149
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +6 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/package.json +1 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +14 -6
- package/ts/src/benchmark-environment-bridge.ts +29 -10
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +489 -171
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +361 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -1,72 +1,38 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
5
|
-
import { BOOKING_READ_ACTION_KINDS,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
assert.equal(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
assert.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { BOOKING_READ_ACTION_KINDS, BOOKING_GAP_CODES, BOOKING_BLOCKER_CODES, BOOKING_SURFACE_SCHEMA_SHA256, bookingSurfaceAllowedActions } from '../src/booking-surface/contracts.js';
|
|
5
|
+
import { validateBookingSurface, validateBookingReadAction, validateBookingSurfaceEvent, validateCriterionBlocker, validateRelaxationApproval, validateApprovalAgainstBlocker } from '../src/booking-surface/validation.js';
|
|
6
|
+
let positive = 0;
|
|
7
|
+
let negative = 0;
|
|
8
|
+
function accept(label, value) {
|
|
9
|
+
assert.equal(validateBookingSurface(value).ok, true, label);
|
|
10
|
+
positive++;
|
|
11
|
+
}
|
|
12
|
+
function reject(label, value) {
|
|
13
|
+
assert.equal(validateBookingSurface(value).ok, false, label);
|
|
14
|
+
negative++;
|
|
15
|
+
}
|
|
16
|
+
assert.deepEqual(bookingSurfaceAllowedActions('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(bookingSurfaceAllowedActions('payment_link'), [
|
|
15
23
|
'search.patch',
|
|
16
24
|
'search.run',
|
|
17
25
|
'results.view.patch',
|
|
18
26
|
'hotel.focus',
|
|
19
|
-
'hotel.select'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
assert.deepEqual(BOOKING_RECEIPT_STATUSES, [
|
|
29
|
-
'applied',
|
|
30
|
-
'needs_input',
|
|
31
|
-
'partial',
|
|
32
|
-
'no_match',
|
|
33
|
-
'unavailable',
|
|
34
|
-
'changed',
|
|
35
|
-
'stale',
|
|
36
|
-
'unsupported',
|
|
37
|
-
'failed'
|
|
38
|
-
]);
|
|
39
|
-
assert.deepEqual(BOOKING_SURFACE_EVENT_KINDS, [
|
|
40
|
-
'status',
|
|
41
|
-
'question',
|
|
42
|
-
'operation',
|
|
43
|
-
'explanation',
|
|
44
|
-
'terminal',
|
|
45
|
-
'error'
|
|
46
|
-
]);
|
|
47
|
-
assert.deepEqual(EMBEDDED_BOOKING_CAPABILITY_IDS, [
|
|
48
|
-
'search-hotels',
|
|
49
|
-
'refine-results',
|
|
50
|
-
'find-room-offers',
|
|
51
|
-
'compare-offers',
|
|
52
|
-
'prepare-booking',
|
|
53
|
-
'observe-booking'
|
|
54
|
-
]);
|
|
55
|
-
const registeredActions = embeddedBookingProfile.capabilities.flatMap((capability)=>capability.actions);
|
|
56
|
-
assert.deepEqual([
|
|
57
|
-
...new Set(registeredActions)
|
|
58
|
-
].sort(), [
|
|
59
|
-
...BOOKING_READ_ACTION_KINDS
|
|
60
|
-
].sort(), 'profile registers the full read-action closure exactly once');
|
|
61
|
-
const forbiddenWriteActions = new Set([
|
|
62
|
-
'book',
|
|
63
|
-
'trade.book',
|
|
64
|
-
'gotry_book'
|
|
65
|
-
]);
|
|
66
|
-
assert.ok(!registeredActions.some((action)=>forbiddenWriteActions.has(action)), 'embedded profile exposes no Book action');
|
|
67
|
-
assert.ok(embeddedBookingProfile.capabilities.every((capability)=>capability.effect === 'read'), 'all embedded capabilities are read-side effects');
|
|
68
|
-
const ingressWorkspace = {
|
|
69
|
-
schemaVersion: 'booking.surface.v1',
|
|
27
|
+
'hotel.select'
|
|
28
|
+
], 'payment_link UAT availability observation is least privilege');
|
|
29
|
+
assert.ok(!bookingSurfaceAllowedActions('storefront').includes('offers.query'), 'storefront availability cannot expand into offers');
|
|
30
|
+
assert.ok(!bookingSurfaceAllowedActions('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',
|
|
34
|
+
contextRef: 'ctx-1',
|
|
35
|
+
surface: 'tenant',
|
|
70
36
|
revision: 0,
|
|
71
37
|
locale: 'zh-CN',
|
|
72
38
|
currency: 'AED',
|
|
@@ -76,318 +42,1180 @@ const ingressWorkspace = {
|
|
|
76
42
|
},
|
|
77
43
|
visibleHotels: [],
|
|
78
44
|
loadedOffers: [],
|
|
79
|
-
shortlistedOfferRefs: []
|
|
45
|
+
shortlistedOfferRefs: [],
|
|
46
|
+
capabilities: {
|
|
47
|
+
surface: 'tenant',
|
|
48
|
+
allowedActions: [
|
|
49
|
+
...BOOKING_READ_ACTION_KINDS
|
|
50
|
+
]
|
|
51
|
+
}
|
|
80
52
|
};
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.map((kind)=>[
|
|
113
|
+
kind,
|
|
114
|
+
{
|
|
115
|
+
schemaVersion: 'booking.surface',
|
|
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){
|
|
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',
|
|
269
|
+
kind: 'user.turn',
|
|
270
|
+
taskId: 'versioned-task',
|
|
271
|
+
turnId: 'versioned-turn',
|
|
272
|
+
workspace: versionedWorkspace,
|
|
86
273
|
request: {
|
|
87
|
-
text: '
|
|
274
|
+
text: 'find hotels'
|
|
88
275
|
}
|
|
89
276
|
};
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
}
|
|
92
287
|
});
|
|
93
|
-
|
|
94
|
-
...
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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'
|
|
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(validateCriterionBlocker(blocker).ok, true);
|
|
427
|
+
positive++;
|
|
428
|
+
assert.equal(validateRelaxationApproval(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(validateCriterionBlocker({
|
|
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(validateCriterionBlocker({
|
|
448
|
+
...blocker,
|
|
449
|
+
[field]: field === 'criterionPath' ? 'open.path' : 'dynamic'
|
|
450
|
+
}).ok, false);
|
|
451
|
+
negative++;
|
|
452
|
+
}
|
|
453
|
+
assert.equal(validateCriterionBlocker({
|
|
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(validateCriterionBlocker({
|
|
465
|
+
...blocker,
|
|
466
|
+
evidence: {
|
|
467
|
+
...blocker.evidence,
|
|
468
|
+
open: true
|
|
469
|
+
}
|
|
470
|
+
}).ok, false);
|
|
471
|
+
negative++;
|
|
472
|
+
const receipt = {
|
|
473
|
+
schemaVersion: 'booking.surface',
|
|
117
474
|
kind: 'action.receipt',
|
|
118
|
-
actionId: '
|
|
475
|
+
actionId: 'a-search-run',
|
|
119
476
|
contextRef: 'ctx-1',
|
|
120
|
-
status: '
|
|
477
|
+
status: 'partial',
|
|
121
478
|
revision: 1,
|
|
122
479
|
observation: {
|
|
123
|
-
kind: '
|
|
124
|
-
|
|
480
|
+
kind: 'gap',
|
|
481
|
+
code: 'search_failed',
|
|
482
|
+
factRefs: [
|
|
483
|
+
'fact-1'
|
|
484
|
+
]
|
|
125
485
|
},
|
|
126
486
|
resultContract: {
|
|
127
|
-
outcome: '
|
|
128
|
-
hardCriteriaMet:
|
|
129
|
-
factRefs: [
|
|
130
|
-
|
|
487
|
+
outcome: 'partial',
|
|
488
|
+
hardCriteriaMet: false,
|
|
489
|
+
factRefs: [
|
|
490
|
+
'fact-1'
|
|
491
|
+
],
|
|
492
|
+
gapCodes: [
|
|
493
|
+
...BOOKING_GAP_CODES
|
|
494
|
+
],
|
|
495
|
+
blockers: [
|
|
496
|
+
blocker
|
|
497
|
+
],
|
|
498
|
+
relaxationsApplied: []
|
|
131
499
|
}
|
|
132
500
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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,
|
|
138
528
|
observation: {
|
|
139
|
-
|
|
140
|
-
|
|
529
|
+
...receipt.observation,
|
|
530
|
+
open: true
|
|
141
531
|
}
|
|
142
|
-
})
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
532
|
+
});
|
|
533
|
+
for (const code of BOOKING_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'
|
|
151
614
|
]
|
|
152
615
|
}
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
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',
|
|
156
657
|
kind: 'user.turn',
|
|
157
658
|
taskId: 'task-1',
|
|
158
|
-
|
|
659
|
+
turnId: 'turn-1',
|
|
660
|
+
workspace,
|
|
159
661
|
request: {
|
|
160
|
-
text:
|
|
662
|
+
text: 'find hotels'
|
|
161
663
|
}
|
|
162
664
|
};
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
allowedActions: [
|
|
173
|
-
...BOOKING_READ_ACTION_KINDS
|
|
174
|
-
]
|
|
175
|
-
}
|
|
665
|
+
const ingress = {
|
|
666
|
+
schemaVersion: 'booking.surface',
|
|
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'
|
|
176
674
|
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
...strictWorkspace,
|
|
182
|
-
results: {}
|
|
183
|
-
}
|
|
184
|
-
}).ok, false, 'canonical turn rejects results without status');
|
|
185
|
-
const { contextRef: _omitted, ...workspaceWithoutContext } = strictWorkspace;
|
|
186
|
-
assert.equal(validateBookingCopilotTurnV1({
|
|
187
|
-
...plannerTurn,
|
|
188
|
-
workspace: workspaceWithoutContext
|
|
189
|
-
}).ok, false, 'planner turn requires the server-minted contextRef');
|
|
190
|
-
const statusEvent = {
|
|
191
|
-
schemaVersion: 'booking.surface.v1',
|
|
192
|
-
eventId: 'event-1',
|
|
675
|
+
};
|
|
676
|
+
const continuation = {
|
|
677
|
+
schemaVersion: 'booking.surface',
|
|
678
|
+
kind: 'action.receipt.continuation',
|
|
193
679
|
taskId: 'task-1',
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
emittedAt: '2026-08-30T12:00:00.000Z',
|
|
197
|
-
kind: 'status',
|
|
198
|
-
status: 'submitted'
|
|
680
|
+
workspace,
|
|
681
|
+
receipt
|
|
199
682
|
};
|
|
200
|
-
|
|
201
|
-
|
|
683
|
+
accept('valid user turn', turn);
|
|
684
|
+
accept('valid ingress', ingress);
|
|
685
|
+
accept('valid ingress approval carries executable typed approval', {
|
|
686
|
+
...ingress,
|
|
687
|
+
request: {
|
|
688
|
+
text: 'relax must criterion',
|
|
689
|
+
approval
|
|
690
|
+
}
|
|
202
691
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}).ok, false, 'status payload is a closed enum');
|
|
209
|
-
const { status: _status, ...eventBase } = statusEvent;
|
|
210
|
-
assert.equal(validateBookingSurfaceEventV1({
|
|
211
|
-
...eventBase,
|
|
212
|
-
kind: 'question',
|
|
213
|
-
question: {}
|
|
214
|
-
}).ok, false, 'question payload is typed, not presence-only');
|
|
215
|
-
assert.equal(validateBookingSurfaceEventV1({
|
|
216
|
-
...eventBase,
|
|
217
|
-
kind: 'operation',
|
|
218
|
-
action: '{"kind":"search.run"}'
|
|
219
|
-
}).ok, false, 'operation rejects JSON-in-text');
|
|
220
|
-
assert.equal(validateBookingSurfaceEventV1({
|
|
221
|
-
...eventBase,
|
|
222
|
-
kind: 'terminal',
|
|
223
|
-
terminal: {
|
|
224
|
-
status: 'failed',
|
|
225
|
-
summary: 'x',
|
|
226
|
-
factRefs: []
|
|
692
|
+
accept('valid continuation', continuation);
|
|
693
|
+
reject('empty text', {
|
|
694
|
+
...turn,
|
|
695
|
+
request: {
|
|
696
|
+
text: ''
|
|
227
697
|
}
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
...
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
698
|
+
});
|
|
699
|
+
reject('multiple approvals', {
|
|
700
|
+
...turn,
|
|
701
|
+
request: {
|
|
702
|
+
text: 'x',
|
|
703
|
+
approval: [
|
|
704
|
+
approval,
|
|
705
|
+
approval
|
|
706
|
+
]
|
|
236
707
|
}
|
|
708
|
+
});
|
|
709
|
+
reject('ingress multiple approvals', {
|
|
710
|
+
...ingress,
|
|
711
|
+
request: {
|
|
712
|
+
text: 'x',
|
|
713
|
+
approval: [
|
|
714
|
+
approval,
|
|
715
|
+
approval
|
|
716
|
+
]
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
accept('text only remains non-approval', {
|
|
720
|
+
...turn,
|
|
721
|
+
request: {
|
|
722
|
+
text: 'please relax must'
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
const selectedIngressWorkspace = {
|
|
726
|
+
...ingressWorkspace,
|
|
727
|
+
visibleHotels: [
|
|
728
|
+
{
|
|
729
|
+
hotelRef: 'hotel-1',
|
|
730
|
+
name: 'Hotel 1',
|
|
731
|
+
factRefs: [
|
|
732
|
+
'hotel-fact-1'
|
|
733
|
+
]
|
|
734
|
+
}
|
|
735
|
+
],
|
|
736
|
+
loadedOffers: [
|
|
737
|
+
{
|
|
738
|
+
offerRef: 'offer-1',
|
|
739
|
+
offerVersionRef: 'offer-1:v1',
|
|
740
|
+
hotelRef: 'hotel-1',
|
|
741
|
+
evidenceLevel: 'rate_loaded',
|
|
742
|
+
factRefs: [
|
|
743
|
+
'offer-fact-1'
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
],
|
|
747
|
+
focusedHotelRef: 'hotel-1',
|
|
748
|
+
selectedOfferRef: 'offer-1'
|
|
237
749
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
assert.equal(bookingOperationFromEvent(errorWithJsonLookingText), null, 'JSON-looking error text never becomes an executable action');
|
|
242
|
-
const validAction = {
|
|
243
|
-
schemaVersion: 'booking.surface.v1',
|
|
244
|
-
kind: 'search.run',
|
|
245
|
-
actionId: 'action-1',
|
|
246
|
-
contextRef: 'ctx-server-minted',
|
|
247
|
-
expectedRevision: 0,
|
|
248
|
-
reason: 'Run the existing search form.',
|
|
249
|
-
factRefs: [],
|
|
250
|
-
input: {}
|
|
251
|
-
};
|
|
252
|
-
assert.deepEqual(validateBookingReadActionV1(validAction), {
|
|
253
|
-
ok: true
|
|
750
|
+
accept('ingress preserves visible focus and loaded selection', {
|
|
751
|
+
...ingress,
|
|
752
|
+
workspace: selectedIngressWorkspace
|
|
254
753
|
});
|
|
255
|
-
|
|
256
|
-
...
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
amount: '1000.00',
|
|
279
|
-
currency: 'AED',
|
|
280
|
-
sourceFactRef: 'fact-budget-1'
|
|
281
|
-
}
|
|
282
|
-
}
|
|
754
|
+
reject('ingress rejects invisible focus', {
|
|
755
|
+
...ingress,
|
|
756
|
+
workspace: {
|
|
757
|
+
...selectedIngressWorkspace,
|
|
758
|
+
focusedHotelRef: 'hotel-other'
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
reject('ingress rejects unloaded selection', {
|
|
762
|
+
...ingress,
|
|
763
|
+
workspace: {
|
|
764
|
+
...selectedIngressWorkspace,
|
|
765
|
+
selectedOfferRef: 'offer-other'
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
reject('ingress rejects selection whose hotel is not visible', {
|
|
769
|
+
...ingress,
|
|
770
|
+
workspace: {
|
|
771
|
+
...selectedIngressWorkspace,
|
|
772
|
+
visibleHotels: [
|
|
773
|
+
{
|
|
774
|
+
hotelRef: 'hotel-other',
|
|
775
|
+
name: 'Other',
|
|
776
|
+
factRefs: []
|
|
283
777
|
}
|
|
778
|
+
]
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
reject('ingress never accepts verified authority', {
|
|
782
|
+
...ingress,
|
|
783
|
+
workspace: {
|
|
784
|
+
...selectedIngressWorkspace,
|
|
785
|
+
verifiedOfferRef: 'verified-offer-1'
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
reject('missing user turn identity', {
|
|
789
|
+
...turn,
|
|
790
|
+
turnId: undefined
|
|
791
|
+
});
|
|
792
|
+
reject('missing ingress request key', {
|
|
793
|
+
...ingress,
|
|
794
|
+
requestKey: undefined
|
|
795
|
+
});
|
|
796
|
+
reject('unsafe user turn identity', {
|
|
797
|
+
...turn,
|
|
798
|
+
turnId: 'Bearer token'
|
|
799
|
+
});
|
|
800
|
+
reject('unsafe ingress task handle', {
|
|
801
|
+
...ingress,
|
|
802
|
+
taskHandle: 'user@example.com'
|
|
803
|
+
});
|
|
804
|
+
reject('missing user task identity', {
|
|
805
|
+
...turn,
|
|
806
|
+
taskId: undefined
|
|
807
|
+
});
|
|
808
|
+
reject('browser task identity injection', {
|
|
809
|
+
...ingress,
|
|
810
|
+
taskId: 'browser-task'
|
|
811
|
+
});
|
|
812
|
+
reject('browser turn identity injection', {
|
|
813
|
+
...ingress,
|
|
814
|
+
turnId: 'browser-turn'
|
|
815
|
+
});
|
|
816
|
+
reject('browser context identity injection', {
|
|
817
|
+
...ingress,
|
|
818
|
+
contextRef: null
|
|
819
|
+
});
|
|
820
|
+
reject('browser verified authority injection', {
|
|
821
|
+
...ingress,
|
|
822
|
+
workspace: {
|
|
823
|
+
...ingressWorkspace,
|
|
824
|
+
verifiedOfferRef: 'verified-offer-1'
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
assert.equal(validateCriterionBlocker({
|
|
828
|
+
...blocker,
|
|
829
|
+
evidence: {
|
|
830
|
+
factRefs: [],
|
|
831
|
+
gapCodes: []
|
|
832
|
+
}
|
|
833
|
+
}).ok, false);
|
|
834
|
+
negative++;
|
|
835
|
+
assert.equal(validateApprovalAgainstBlocker({
|
|
836
|
+
...approval,
|
|
837
|
+
blockerId: 'wrong'
|
|
838
|
+
}, blocker).ok, false);
|
|
839
|
+
negative++;
|
|
840
|
+
const continuationBlocked = {
|
|
841
|
+
...continuation,
|
|
842
|
+
receipt: {
|
|
843
|
+
...receipt,
|
|
844
|
+
resultContract: {
|
|
845
|
+
...receipt.resultContract,
|
|
846
|
+
hardCriteriaMet: true
|
|
284
847
|
}
|
|
285
848
|
}
|
|
286
849
|
};
|
|
287
|
-
|
|
288
|
-
|
|
850
|
+
reject('continuation hard criteria with blocker', continuationBlocked);
|
|
851
|
+
reject('formatted phone', {
|
|
852
|
+
...turn,
|
|
853
|
+
request: {
|
|
854
|
+
text: '+971 (50) 123 4567'
|
|
855
|
+
}
|
|
289
856
|
});
|
|
290
|
-
|
|
291
|
-
...
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
857
|
+
reject('dotted phone', {
|
|
858
|
+
...turn,
|
|
859
|
+
request: {
|
|
860
|
+
text: '+971.50.123.4567'
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
reject('slash phone', {
|
|
864
|
+
...turn,
|
|
865
|
+
request: {
|
|
866
|
+
text: 'call +971/50/123/4567'
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
reject('comma phone', {
|
|
870
|
+
...turn,
|
|
871
|
+
request: {
|
|
872
|
+
text: 'call +971,50,123,4567'
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
reject('unicode dash phone', {
|
|
876
|
+
...turn,
|
|
877
|
+
request: {
|
|
878
|
+
text: 'call +971—50—123—4567'
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
reject('nbsp phone', {
|
|
882
|
+
...turn,
|
|
883
|
+
request: {
|
|
884
|
+
text: '+971\u00a050\u00a0123\u00a04567'
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
reject('embedded phone', {
|
|
888
|
+
...turn,
|
|
889
|
+
request: {
|
|
890
|
+
text: 'call +971 50 123 4567 please'
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
reject('payment card', {
|
|
894
|
+
...turn,
|
|
895
|
+
request: {
|
|
896
|
+
text: '4111 1111 1111 1111'
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
reject('supplier cost', {
|
|
900
|
+
...turn,
|
|
901
|
+
request: {
|
|
902
|
+
text: 'supplier net cost AED 42'
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
reject('supplier cost colon', {
|
|
906
|
+
...turn,
|
|
907
|
+
request: {
|
|
908
|
+
text: 'supplier net cost: AED 42'
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
reject('supplier possessive cost', {
|
|
912
|
+
...turn,
|
|
913
|
+
request: {
|
|
914
|
+
text: "supplier's net cost AED 42"
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
reject('supplier cost reversed currency', {
|
|
918
|
+
...turn,
|
|
919
|
+
request: {
|
|
920
|
+
text: 'supplier cost 42 AED'
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
reject('supplier wholesale', {
|
|
924
|
+
...turn,
|
|
925
|
+
request: {
|
|
926
|
+
text: 'supplier wholesale rate AED 42'
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
reject('supplier base bypass', {
|
|
930
|
+
...turn,
|
|
931
|
+
request: {
|
|
932
|
+
text: 'supplier base cost AED 42'
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
reject('vendor cost', {
|
|
936
|
+
...turn,
|
|
937
|
+
request: {
|
|
938
|
+
text: 'vendor net cost AED 42'
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
reject('vendor compact amount', {
|
|
942
|
+
...turn,
|
|
943
|
+
request: {
|
|
944
|
+
text: 'vendor price AED42'
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
reject('internal supplier cost', {
|
|
948
|
+
...turn,
|
|
949
|
+
request: {
|
|
950
|
+
text: 'internal supplier cost AED 42'
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
reject('internal reversed amount', {
|
|
954
|
+
...turn,
|
|
955
|
+
request: {
|
|
956
|
+
text: 'internal rate 42 USD'
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
reject('contact email bypass', {
|
|
960
|
+
...turn,
|
|
961
|
+
request: {
|
|
962
|
+
text: 'contact user@hotelbyte.com'
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
reject('quoted email local part', {
|
|
966
|
+
...turn,
|
|
967
|
+
request: {
|
|
968
|
+
text: 'contact "user"@hotelbyte.com'
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
reject('unicode email local part', {
|
|
972
|
+
...turn,
|
|
973
|
+
request: {
|
|
974
|
+
text: '联系 用户@hotelbyte.com'
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
reject('technical-looking real mailbox', {
|
|
978
|
+
...turn,
|
|
979
|
+
request: {
|
|
980
|
+
text: 'version@company.com'
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
accept('benign trace identifier', {
|
|
984
|
+
...turn,
|
|
985
|
+
request: {
|
|
986
|
+
text: 'trace 0000000000000000'
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
accept('benign sk-ills sentence', {
|
|
990
|
+
...turn,
|
|
991
|
+
request: {
|
|
992
|
+
text: 'sk-ills are useful'
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
accept('benign version email', {
|
|
996
|
+
...turn,
|
|
997
|
+
request: {
|
|
998
|
+
text: 'version@release.example'
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
accept('benign schema identifier', {
|
|
1002
|
+
...turn,
|
|
1003
|
+
request: {
|
|
1004
|
+
text: 'schema@v2.example'
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
accept('benign budget', {
|
|
1008
|
+
...turn,
|
|
1009
|
+
request: {
|
|
1010
|
+
text: 'budget AED 1000'
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
accept('benign total price', {
|
|
1014
|
+
...turn,
|
|
1015
|
+
request: {
|
|
1016
|
+
text: 'total price under AED 1000'
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
const common = {
|
|
1020
|
+
schemaVersion: 'booking.surface',
|
|
1021
|
+
eventId: 'e-1',
|
|
1022
|
+
taskId: 'task-1',
|
|
1023
|
+
contextRef: 'ctx-1',
|
|
1024
|
+
sequence: 1,
|
|
1025
|
+
emittedAt: '2026-08-30T12:00:00.000Z'
|
|
1026
|
+
};
|
|
1027
|
+
const questionEvent = {
|
|
1028
|
+
...common,
|
|
1029
|
+
kind: 'question',
|
|
1030
|
+
question: {
|
|
1031
|
+
questionId: 'q-1',
|
|
1032
|
+
prompt: 'Relax?',
|
|
1033
|
+
missingFields: [],
|
|
1034
|
+
type: 'relaxation_approval_required',
|
|
1035
|
+
blocker,
|
|
1036
|
+
approvalOptions: [
|
|
1037
|
+
{
|
|
1038
|
+
approval
|
|
298
1039
|
}
|
|
1040
|
+
]
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const events = [
|
|
1044
|
+
{
|
|
1045
|
+
...common,
|
|
1046
|
+
kind: 'status',
|
|
1047
|
+
status: 'working'
|
|
1048
|
+
},
|
|
1049
|
+
questionEvent,
|
|
1050
|
+
{
|
|
1051
|
+
...common,
|
|
1052
|
+
kind: 'operation',
|
|
1053
|
+
action: actions['search.run']
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
...common,
|
|
1057
|
+
kind: 'explanation',
|
|
1058
|
+
explanation: {
|
|
1059
|
+
text: 'x',
|
|
1060
|
+
factRefs: []
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
...common,
|
|
1065
|
+
kind: 'terminal',
|
|
1066
|
+
terminal: {
|
|
1067
|
+
status: 'completed',
|
|
1068
|
+
summary: 'x',
|
|
1069
|
+
factRefs: []
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
...common,
|
|
1074
|
+
kind: 'error',
|
|
1075
|
+
error: {
|
|
1076
|
+
code: 'ERR',
|
|
1077
|
+
message: 'x',
|
|
1078
|
+
retryable: false
|
|
299
1079
|
}
|
|
300
1080
|
}
|
|
301
|
-
|
|
302
|
-
assert.equal(
|
|
303
|
-
...
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
currency: 'AED',
|
|
312
|
-
sourceFactRef: 'fact-budget-1'
|
|
313
|
-
}
|
|
1081
|
+
];
|
|
1082
|
+
assert.equal(validateBookingSurfaceEvent({
|
|
1083
|
+
...questionEvent,
|
|
1084
|
+
question: {
|
|
1085
|
+
...questionEvent.question,
|
|
1086
|
+
approvalOptions: [
|
|
1087
|
+
{
|
|
1088
|
+
approval: {
|
|
1089
|
+
...approval,
|
|
1090
|
+
blockerId: 'wrong'
|
|
314
1091
|
}
|
|
315
1092
|
}
|
|
316
|
-
|
|
1093
|
+
]
|
|
317
1094
|
}
|
|
318
|
-
}).ok, false
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1095
|
+
}).ok, false);
|
|
1096
|
+
negative++;
|
|
1097
|
+
assert.equal(validateBookingSurfaceEvent({
|
|
1098
|
+
...questionEvent,
|
|
1099
|
+
question: {
|
|
1100
|
+
...questionEvent.question,
|
|
1101
|
+
approvalOptions: [
|
|
1102
|
+
{
|
|
1103
|
+
approval: {
|
|
1104
|
+
...approval,
|
|
1105
|
+
taskId: 'task-other'
|
|
1106
|
+
}
|
|
328
1107
|
}
|
|
329
|
-
|
|
1108
|
+
]
|
|
330
1109
|
}
|
|
331
|
-
}).ok, false
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
'Twin'
|
|
344
|
-
]
|
|
345
|
-
},
|
|
346
|
-
guest: {
|
|
347
|
-
name: 'x'
|
|
1110
|
+
}).ok, false);
|
|
1111
|
+
negative++;
|
|
1112
|
+
assert.equal(validateBookingSurfaceEvent({
|
|
1113
|
+
...questionEvent,
|
|
1114
|
+
question: {
|
|
1115
|
+
...questionEvent.question,
|
|
1116
|
+
approvalOptions: [
|
|
1117
|
+
{
|
|
1118
|
+
approval: {
|
|
1119
|
+
...approval,
|
|
1120
|
+
contextRef: 'ctx-other'
|
|
1121
|
+
}
|
|
348
1122
|
}
|
|
349
|
-
|
|
1123
|
+
]
|
|
350
1124
|
}
|
|
351
|
-
}).ok, false
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1125
|
+
}).ok, false);
|
|
1126
|
+
negative++;
|
|
1127
|
+
for (const event of events)accept(`valid event ${event.kind}`, event);
|
|
1128
|
+
assert.equal(validateBookingSurfaceEvent({
|
|
1129
|
+
...events[5],
|
|
1130
|
+
eventId: 'event-4242-4242-4242-4242'
|
|
1131
|
+
}).ok, true);
|
|
1132
|
+
positive++;
|
|
1133
|
+
assert.equal(validateBookingSurfaceEvent({
|
|
1134
|
+
...events[3],
|
|
1135
|
+
explanation: {
|
|
1136
|
+
text: 'card 4111 1111 1111 1111',
|
|
1137
|
+
factRefs: []
|
|
1138
|
+
}
|
|
1139
|
+
}).ok, false);
|
|
1140
|
+
negative++;
|
|
1141
|
+
reject('event mixed payload', {
|
|
1142
|
+
...events[0],
|
|
1143
|
+
question: {}
|
|
1144
|
+
});
|
|
1145
|
+
reject('event missing payload', {
|
|
1146
|
+
...events[1],
|
|
1147
|
+
question: undefined
|
|
1148
|
+
});
|
|
1149
|
+
reject('event open nested', {
|
|
1150
|
+
...events[4],
|
|
1151
|
+
terminal: {
|
|
1152
|
+
status: 'completed',
|
|
1153
|
+
summary: 'x',
|
|
366
1154
|
factRefs: [],
|
|
367
|
-
|
|
1155
|
+
open: true
|
|
368
1156
|
}
|
|
369
|
-
};
|
|
370
|
-
assert.deepEqual(validateActionReceiptV1(validReceipt), {
|
|
371
|
-
ok: true
|
|
372
1157
|
});
|
|
373
|
-
|
|
374
|
-
...
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
1158
|
+
reject('request key whitespace', {
|
|
1159
|
+
...ingress,
|
|
1160
|
+
requestKey: 'request key'
|
|
1161
|
+
});
|
|
1162
|
+
reject('request key newline', {
|
|
1163
|
+
...ingress,
|
|
1164
|
+
requestKey: 'request-1\nnext'
|
|
1165
|
+
});
|
|
1166
|
+
reject('request key path', {
|
|
1167
|
+
...ingress,
|
|
1168
|
+
requestKey: 'request/path'
|
|
1169
|
+
});
|
|
1170
|
+
reject('request key email', {
|
|
1171
|
+
...ingress,
|
|
1172
|
+
requestKey: 'user@example.com'
|
|
1173
|
+
});
|
|
1174
|
+
reject('request key secret-like', {
|
|
1175
|
+
...ingress,
|
|
1176
|
+
requestKey: 'sk-live-abcdefghijkl'
|
|
1177
|
+
});
|
|
1178
|
+
reject('request key too short', {
|
|
1179
|
+
...ingress,
|
|
1180
|
+
requestKey: 'r1'
|
|
1181
|
+
});
|
|
1182
|
+
accept('ISO date and currency', {
|
|
1183
|
+
...workspace,
|
|
1184
|
+
searchDraft: {
|
|
1185
|
+
stay: {
|
|
1186
|
+
checkIn: '2026-09-01',
|
|
1187
|
+
checkOut: '2026-09-03'
|
|
1188
|
+
}
|
|
388
1189
|
}
|
|
389
|
-
})
|
|
390
|
-
|
|
1190
|
+
});
|
|
1191
|
+
reject('email secret', {
|
|
1192
|
+
...turn,
|
|
1193
|
+
request: {
|
|
1194
|
+
text: 'user@example.com'
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
reject('Bearer secret', {
|
|
1198
|
+
...turn,
|
|
1199
|
+
request: {
|
|
1200
|
+
text: 'Bearer abc'
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
reject('sk secret', {
|
|
1204
|
+
...turn,
|
|
1205
|
+
request: {
|
|
1206
|
+
text: 'sk-live-abcdefghijkl'
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
reject('JWT secret', {
|
|
1210
|
+
...turn,
|
|
1211
|
+
request: {
|
|
1212
|
+
text: 'eyJabc.def.ghi'
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
accept('E164-like ordinary date safe', turn);
|
|
1216
|
+
assert.equal(createHash('sha256').update(readFileSync(new URL('../../schemas/booking.surface.schema.json', import.meta.url))).digest('hex'), BOOKING_SURFACE_SCHEMA_SHA256);
|
|
1217
|
+
console.log(`BOOKING SURFACE CONTRACT PROOF: positive=${positive} negative=${negative}`);
|
|
1218
|
+
assert.ok(positive >= 65 && negative >= 105);
|
|
391
1219
|
|
|
392
1220
|
|
|
393
1221
|
//# sourceURL=ts/scripts/booking-surface-contract-proof-tests.ts
|