@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20
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 +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +309 -30
- package/bin/gotry-inner.js +74 -23
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +22 -8
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +307 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/hbcli.js +28 -2
- 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 +305 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
- package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
- 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 +113 -2
- 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 +175 -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/hbcli-tests.js +38 -1
- 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 +39 -16
- package/dist/src/benchmark-environment-bridge.js +316 -52
- 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 +739 -150
- 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 +7 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/hbcli.ts +53 -4
- 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/dsh-runtime/vendor/README.md +60 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
- package/ts/package.json +9 -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 +54 -15
- package/ts/src/benchmark-environment-bridge.ts +234 -46
- 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 +492 -172
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +365 -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,14 +1,14 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
2
|
+
export const MAX_HOTELS_PER_TASK = 5;
|
|
3
|
+
export const MAX_OFFERS_PER_HOTEL_GENERATION = 3;
|
|
4
|
+
export const MAX_OFFER_CHECKS_PER_HOTEL = 2;
|
|
5
|
+
export const MAX_OFFER_QUERIES_PER_HOTEL = 2;
|
|
6
6
|
function unique(values) {
|
|
7
7
|
return [
|
|
8
8
|
...new Set(values)
|
|
9
9
|
];
|
|
10
10
|
}
|
|
11
|
-
export function
|
|
11
|
+
export function assertWorkspaceLoadedOfferRefsUnique(workspace) {
|
|
12
12
|
if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer)=>offer.offerRef)).size) throw new Error('availability_duplicate_offer_ref');
|
|
13
13
|
if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer)=>offer.offerVersionRef)).size) throw new Error('availability_duplicate_offer_version_ref');
|
|
14
14
|
}
|
|
@@ -20,20 +20,20 @@ function stable(value) {
|
|
|
20
20
|
stable(value[key])
|
|
21
21
|
]));
|
|
22
22
|
}
|
|
23
|
-
export function
|
|
23
|
+
export function digest(value) {
|
|
24
24
|
return createHash('sha256').update(JSON.stringify(stable(value))).digest('hex');
|
|
25
25
|
}
|
|
26
|
-
function
|
|
26
|
+
function workspaceDigest(workspace) {
|
|
27
27
|
const { contextRef: _context, ...bound } = workspace;
|
|
28
|
-
return
|
|
28
|
+
return digest(bound);
|
|
29
29
|
}
|
|
30
30
|
function offerRefsForHotel(workspace, hotelRef, selectedOfferRef) {
|
|
31
31
|
const refs = unique(workspace.loadedOffers.filter((offer)=>offer.hotelRef === hotelRef).map((offer)=>offer.offerRef));
|
|
32
32
|
if (selectedOfferRef) return unique([
|
|
33
33
|
selectedOfferRef,
|
|
34
34
|
...refs
|
|
35
|
-
]).slice(0,
|
|
36
|
-
if (refs.length >
|
|
35
|
+
]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION);
|
|
36
|
+
if (refs.length > MAX_OFFERS_PER_HOTEL_GENERATION) throw new Error('availability_offer_limit_exceeded');
|
|
37
37
|
return refs;
|
|
38
38
|
}
|
|
39
39
|
function loadedOfferVersion(workspace, offerRef, offerVersionRef) {
|
|
@@ -45,7 +45,7 @@ function legalOfferRefsForHotel(workspace, hotel, refs) {
|
|
|
45
45
|
const loaded = workspace.loadedOffers.find((offer)=>offer.offerRef === ref && offer.hotelRef === hotel.hotelRef);
|
|
46
46
|
return Boolean(loaded && !hotel.tombstonedOfferVersionRefs.includes(loaded.offerVersionRef));
|
|
47
47
|
});
|
|
48
|
-
if (legal.length >
|
|
48
|
+
if (legal.length > MAX_OFFERS_PER_HOTEL_GENERATION) throw new Error('availability_offer_limit_exceeded');
|
|
49
49
|
return unique(legal);
|
|
50
50
|
}
|
|
51
51
|
function copyHotel(hotel) {
|
|
@@ -124,7 +124,7 @@ function isCriterion(value, valueCheck) {
|
|
|
124
124
|
function isMoney(value) {
|
|
125
125
|
return isPlainRecord(value) && Object.keys(value).length === 3 && typeof value.amount === 'string' && Boolean(value.amount) && typeof value.currency === 'string' && Boolean(value.currency) && typeof value.sourceFactRef === 'string' && Boolean(value.sourceFactRef);
|
|
126
126
|
}
|
|
127
|
-
function
|
|
127
|
+
function isOfferCriteria(value) {
|
|
128
128
|
if (!isPlainRecord(value)) return false;
|
|
129
129
|
const allowed = [
|
|
130
130
|
'roomType',
|
|
@@ -160,13 +160,13 @@ function isOfferCriteriaV2(value) {
|
|
|
160
160
|
}
|
|
161
161
|
function bindCriteria(next, criteriaDigest, criteria, required = false) {
|
|
162
162
|
if (criteria !== undefined) {
|
|
163
|
-
if (!
|
|
163
|
+
if (!isOfferCriteria(criteria) || digest(criteria) !== criteriaDigest) throw new Error('availability_criteria_invalid');
|
|
164
164
|
if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest) throw new Error('availability_criteria_changed');
|
|
165
|
-
if (next.criteria &&
|
|
165
|
+
if (next.criteria && digest(next.criteria) !== criteriaDigest) throw new Error('availability_criteria_changed');
|
|
166
166
|
next.criteria = structuredClone(criteria);
|
|
167
167
|
next.criteriaDigest = criteriaDigest;
|
|
168
168
|
} else if (required && !next.criteria) throw new Error('availability_criteria_required');
|
|
169
|
-
else if (next.criteria && next.criteriaDigest !==
|
|
169
|
+
else if (next.criteria && next.criteriaDigest !== digest(next.criteria)) throw new Error('availability_criteria_mismatch');
|
|
170
170
|
else if (!next.criteria && criteriaDigest) {
|
|
171
171
|
if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest) throw new Error('availability_criteria_changed');
|
|
172
172
|
next.criteriaDigest = criteriaDigest;
|
|
@@ -184,7 +184,7 @@ function terminalFor(state) {
|
|
|
184
184
|
reason: 'confirmed',
|
|
185
185
|
evidence: 'conclusive'
|
|
186
186
|
};
|
|
187
|
-
if (hotels.some((hotel)=>hotel.checksIssued <
|
|
187
|
+
if (hotels.some((hotel)=>hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && (hotel.status === 'active' || hotel.status === 'unvisited' || hotel.freshOffersRequired && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL))) return undefined;
|
|
188
188
|
const conclusive = hotels.every((hotel)=>hotel.status === 'negative');
|
|
189
189
|
return {
|
|
190
190
|
code: conclusive ? 'availability_exhausted_complete' : 'availability_exhausted_inconclusive',
|
|
@@ -224,8 +224,8 @@ function phaseForActiveHotel(state) {
|
|
|
224
224
|
const active = state.hotels[state.hotelRefs[state.activeHotelOrdinal] ?? ''];
|
|
225
225
|
return active?.status === 'active' && active.currentGeneration?.valid && active.currentOfferRefs.length > 0 && !active.freshOffersRequired ? 'need_check' : 'need_offers';
|
|
226
226
|
}
|
|
227
|
-
export function
|
|
228
|
-
|
|
227
|
+
export function createAvailabilityPolicy(workspace) {
|
|
228
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
229
229
|
return {
|
|
230
230
|
initialized: false,
|
|
231
231
|
recoveryStarted: false,
|
|
@@ -237,11 +237,11 @@ export function createAvailabilityPolicyV2(workspace) {
|
|
|
237
237
|
queryReservations: []
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
|
-
export function
|
|
240
|
+
export function recordVisibleHotels(state, _workspace) {
|
|
241
241
|
return copyState(state);
|
|
242
242
|
}
|
|
243
|
-
export function
|
|
244
|
-
if (workspace)
|
|
243
|
+
export function recordObservedOffersQuery(state, hotelRefs, criteriaDigest, workspace, receipt, sourceActionId, receiptDigest, criteria) {
|
|
244
|
+
if (workspace) assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
245
245
|
if (state.recoveryStarted) return copyState(state);
|
|
246
246
|
const next = copyState(state);
|
|
247
247
|
if (!workspace || !receipt) return next;
|
|
@@ -257,12 +257,12 @@ export function recordObservedOffersQueryV2(state, hotelRefs, criteriaDigest, wo
|
|
|
257
257
|
delete next.criteria;
|
|
258
258
|
delete next.criteriaDigest;
|
|
259
259
|
bindCriteria(next, criteriaDigest, criteria);
|
|
260
|
-
if (!sourceActionId || sourceActionId !== receipt.actionId || !receiptDigest || receiptDigest !==
|
|
260
|
+
if (!sourceActionId || sourceActionId !== receipt.actionId || !receiptDigest || receiptDigest !== digest(receipt)) throw new Error('availability_query_provenance_mismatch');
|
|
261
261
|
next.lastQueryHotelRefs = [
|
|
262
262
|
...new Set(hotelRefs)
|
|
263
263
|
];
|
|
264
264
|
next.lastQueryCriteriaDigest = criteriaDigest || undefined;
|
|
265
|
-
next.lastQueryOfferHotels =
|
|
265
|
+
next.lastQueryOfferHotels = validateOffersReceiptWorkspace(receipt, workspace, hotelRefs);
|
|
266
266
|
next.lastQuerySourceActionId = sourceActionId;
|
|
267
267
|
next.lastQuerySourceReceiptDigest = receiptDigest;
|
|
268
268
|
next.lastQueryWorkspaceRevision = workspace.revision;
|
|
@@ -272,8 +272,8 @@ export function recordObservedOffersQueryV2(state, hotelRefs, criteriaDigest, wo
|
|
|
272
272
|
}
|
|
273
273
|
return next;
|
|
274
274
|
}
|
|
275
|
-
export function
|
|
276
|
-
|
|
275
|
+
export function validateOffersReceiptWorkspace(receipt, workspace, hotelRefs) {
|
|
276
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
277
277
|
if (receipt.observation.kind !== 'offers.state') throw new Error('availability_offers_observation_required');
|
|
278
278
|
const requested = unique(hotelRefs);
|
|
279
279
|
if (!sameSet(receipt.observation.hotelRefs, requested)) throw new Error('availability_offers_hotel_mismatch');
|
|
@@ -304,14 +304,14 @@ export function validateOffersReceiptWorkspaceV2(receipt, workspace, hotelRefs)
|
|
|
304
304
|
function sameSet(a, b) {
|
|
305
305
|
return a.length === b.length && unique(a).sort().every((value, index)=>value === unique(b).sort()[index]);
|
|
306
306
|
}
|
|
307
|
-
export function
|
|
308
|
-
|
|
307
|
+
export function recordOffersQueryIssued(state, hotelRefs, workspace, actionId, criteriaDigest = '', criteria) {
|
|
308
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
309
309
|
const next = copyState(state);
|
|
310
310
|
const requested = unique(hotelRefs);
|
|
311
|
-
if (!requested.length || requested.length >
|
|
311
|
+
if (!requested.length || requested.length > MAX_HOTELS_PER_TASK) throw new Error('availability_hotel_limit_exceeded');
|
|
312
312
|
if (!state.recoveryStarted) throw new Error('availability_recovery_not_started');
|
|
313
313
|
if (state.terminal) {
|
|
314
|
-
if (requested.some((ref)=>state.hotels[ref]?.offerQueriesIssued >=
|
|
314
|
+
if (requested.some((ref)=>state.hotels[ref]?.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL)) throw new Error('availability_offer_query_limit_reached');
|
|
315
315
|
throw new Error('availability_terminal');
|
|
316
316
|
}
|
|
317
317
|
if (requested.some((ref)=>!next.hotels[ref])) throw new Error('availability_hotel_unknown');
|
|
@@ -324,7 +324,7 @@ export function recordOffersQueryIssuedV2(state, hotelRefs, workspace, actionId,
|
|
|
324
324
|
}
|
|
325
325
|
for (const hotelRef of requested){
|
|
326
326
|
const hotel = next.hotels[hotelRef];
|
|
327
|
-
if (hotel.offerQueriesIssued >=
|
|
327
|
+
if (hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL) throw new Error('availability_offer_query_limit_reached');
|
|
328
328
|
hotel.offerQueriesIssued += 1;
|
|
329
329
|
}
|
|
330
330
|
next.queryReservations.push({
|
|
@@ -338,21 +338,21 @@ export function recordOffersQueryIssuedV2(state, hotelRefs, workspace, actionId,
|
|
|
338
338
|
next.initialized = true;
|
|
339
339
|
return next;
|
|
340
340
|
}
|
|
341
|
-
export function
|
|
342
|
-
|
|
341
|
+
export function recordOffersGeneration(state, hotelRefs, workspace, actionId, receiptDigest, receipt, criteriaDigest = '', criteria) {
|
|
342
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
343
343
|
if (!state.recoveryStarted) return copyState(state);
|
|
344
344
|
if (state.terminal) throw new Error('availability_terminal');
|
|
345
345
|
const next = copyState(state);
|
|
346
346
|
const requested = unique(hotelRefs);
|
|
347
|
-
if (!requested.length || requested.length >
|
|
347
|
+
if (!requested.length || requested.length > MAX_HOTELS_PER_TASK) throw new Error('availability_hotel_limit_exceeded');
|
|
348
348
|
const reservation = next.queryReservations.find((query)=>query.actionId === actionId);
|
|
349
349
|
if (!reservation || !sameSet(reservation.hotelRefs, requested)) throw new Error('availability_query_not_reserved');
|
|
350
350
|
if (receipt.actionId !== actionId) throw new Error('availability_query_receipt_mismatch');
|
|
351
|
-
if (receiptDigest !==
|
|
351
|
+
if (receiptDigest !== digest(receipt)) throw new Error('availability_receipt_digest_mismatch');
|
|
352
352
|
if (requested.some((ref)=>!next.hotels[ref])) throw new Error('availability_hotel_unknown');
|
|
353
353
|
else if ((requested.length !== 1 || next.hotelRefs[next.activeHotelOrdinal] !== requested[0]) && (next.attempts.length > 0 || Object.values(next.hotels).some((hotel)=>hotel.generationNo > 0))) throw new Error('availability_hotel_not_active');
|
|
354
354
|
bindCriteria(next, criteriaDigest, criteria);
|
|
355
|
-
if (receipt.observation.kind === 'offers.state')
|
|
355
|
+
if (receipt.observation.kind === 'offers.state') validateOffersReceiptWorkspace(receipt, workspace, requested);
|
|
356
356
|
for (const hotelRef of requested){
|
|
357
357
|
const hotel = next.hotels[hotelRef];
|
|
358
358
|
const partial = receipt.status === 'changed' || receipt.status === 'partial' || receipt.status === 'failed' || receipt.status === 'stale' || receipt.resultContract.outcome === 'partial' || receipt.resultContract.outcome === 'complete' && !receipt.resultContract.hardCriteriaMet || Boolean(receipt.resultContract.blockers.length) || Boolean(receipt.resultContract.gapCodes.length) || Boolean(receipt.observation.kind === 'gap' || receipt.observation.kind === 'offers.state' && receipt.observation.gapCodes?.length);
|
|
@@ -368,18 +368,18 @@ export function recordOffersGenerationV2(state, hotelRefs, workspace, actionId,
|
|
|
368
368
|
hotel.invalidatedOfferRefs = [
|
|
369
369
|
...hotel.currentGeneration?.orderedOfferRefs ?? []
|
|
370
370
|
];
|
|
371
|
-
hotel.freshOffersRequired = hotel.checksIssued <
|
|
371
|
+
hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
|
|
372
372
|
hotel.status = 'inconclusive';
|
|
373
373
|
hotel.lastEvidence = 'inconclusive';
|
|
374
374
|
continue;
|
|
375
375
|
}
|
|
376
376
|
const filteredAllCandidates = candidateRefs.length > 0 && refs.length === 0;
|
|
377
|
-
const noLegalRefsNeedsRefresh = filteredAllCandidates && hotel.offerQueriesIssued <
|
|
377
|
+
const noLegalRefsNeedsRefresh = filteredAllCandidates && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
|
|
378
378
|
hotel.generationNo += 1;
|
|
379
379
|
hotel.generation = hotel.generationNo;
|
|
380
380
|
hotel.currentOfferRefs = partial || noLegalRefsNeedsRefresh ? [] : refs;
|
|
381
381
|
hotel.invalidatedOfferRefs = partial || noLegalRefsNeedsRefresh ? candidateRefs : [];
|
|
382
|
-
hotel.freshOffersRequired = (partial || noLegalRefsNeedsRefresh) && hotel.checksIssued <
|
|
382
|
+
hotel.freshOffersRequired = (partial || noLegalRefsNeedsRefresh) && hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
|
|
383
383
|
hotel.status = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'active' : 'negative';
|
|
384
384
|
hotel.lastEvidence = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'none' : 'unavailable';
|
|
385
385
|
hotel.currentGeneration = {
|
|
@@ -390,7 +390,7 @@ export function recordOffersGenerationV2(state, hotelRefs, workspace, actionId,
|
|
|
390
390
|
receiptDigest,
|
|
391
391
|
workspaceRevision: workspace.revision
|
|
392
392
|
},
|
|
393
|
-
offerSetDigest:
|
|
393
|
+
offerSetDigest: digest(refs),
|
|
394
394
|
orderedOfferRefs: refs,
|
|
395
395
|
evidence: partial ? 'partial' : 'complete',
|
|
396
396
|
valid: refs.length > 0 && !partial
|
|
@@ -412,8 +412,8 @@ export function recordOffersGenerationV2(state, hotelRefs, workspace, actionId,
|
|
|
412
412
|
delete next.terminal;
|
|
413
413
|
return withTerminal(next);
|
|
414
414
|
}
|
|
415
|
-
export function
|
|
416
|
-
|
|
415
|
+
export function canIssueOfferCheck(state, workspace, offerRef, offerVersionRef) {
|
|
416
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
417
417
|
if (!state.recoveryStarted) {
|
|
418
418
|
const loaded = loadedOfferVersion(workspace, offerRef, offerVersionRef);
|
|
419
419
|
return loaded ? {
|
|
@@ -458,7 +458,7 @@ export function canIssueOfferCheckV2(state, workspace, offerRef, offerVersionRef
|
|
|
458
458
|
ok: false,
|
|
459
459
|
code: 'generation_invalid'
|
|
460
460
|
};
|
|
461
|
-
if (hotel.checksIssued >=
|
|
461
|
+
if (hotel.checksIssued >= MAX_OFFER_CHECKS_PER_HOTEL) return {
|
|
462
462
|
ok: false,
|
|
463
463
|
code: 'hotel_check_limit_reached'
|
|
464
464
|
};
|
|
@@ -471,8 +471,8 @@ export function canIssueOfferCheckV2(state, workspace, offerRef, offerVersionRef
|
|
|
471
471
|
generation: hotel.generationNo
|
|
472
472
|
};
|
|
473
473
|
}
|
|
474
|
-
function
|
|
475
|
-
|
|
474
|
+
function startRecoveryAtOffer(state, workspace, offerRef, actionId) {
|
|
475
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
476
476
|
const loaded = workspace.loadedOffers.find((offer)=>offer.offerRef === offerRef);
|
|
477
477
|
if (!loaded) return copyState(state);
|
|
478
478
|
const shortlistHotels = workspace.shortlistedOfferRefs.map((ref)=>workspace.loadedOffers.find((offer)=>offer.offerRef === ref)?.hotelRef ?? state.lastQueryOfferHotels?.[ref]).map((ref, index)=>{
|
|
@@ -483,12 +483,12 @@ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
|
|
|
483
483
|
const candidates = unique([
|
|
484
484
|
loaded.hotelRef,
|
|
485
485
|
...fallbackHotels
|
|
486
|
-
]).slice(0,
|
|
486
|
+
]).slice(0, MAX_HOTELS_PER_TASK);
|
|
487
487
|
const next = copyState(state);
|
|
488
488
|
next.recoveryStarted = true;
|
|
489
489
|
next.recoveryId = `recovery:${actionId}`;
|
|
490
490
|
next.hotelRefs = candidates;
|
|
491
|
-
next.candidateSetDigest =
|
|
491
|
+
next.candidateSetDigest = digest(candidates);
|
|
492
492
|
next.hotels = Object.fromEntries(candidates.map((ref)=>[
|
|
493
493
|
ref,
|
|
494
494
|
newHotel(ref)
|
|
@@ -499,12 +499,12 @@ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
|
|
|
499
499
|
const mapped = state.lastQueryOfferHotels?.[offerRef] === loaded.hotelRef && state.lastQueryWorkspaceRevision === workspace.revision && Boolean(state.lastQuerySourceActionId && state.lastQuerySourceReceiptDigest) && sameSet(mappedRefs, currentRefs);
|
|
500
500
|
if (mapped && state.criteria) {
|
|
501
501
|
next.criteria = structuredClone(state.criteria);
|
|
502
|
-
next.criteriaDigest =
|
|
502
|
+
next.criteriaDigest = digest(next.criteria);
|
|
503
503
|
}
|
|
504
504
|
const refs = mapped && state.lastQueryOfferHotels ? unique([
|
|
505
505
|
offerRef,
|
|
506
506
|
...mappedRefs
|
|
507
|
-
]).slice(0,
|
|
507
|
+
]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION) : offerRefsForHotel(workspace, loaded.hotelRef, offerRef);
|
|
508
508
|
const source = mapped ? {
|
|
509
509
|
kind: 'query_receipt',
|
|
510
510
|
actionId: state.lastQuerySourceActionId,
|
|
@@ -512,7 +512,7 @@ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
|
|
|
512
512
|
workspaceRevision: state.lastQueryWorkspaceRevision
|
|
513
513
|
} : {
|
|
514
514
|
kind: 'workspace_snapshot',
|
|
515
|
-
workspaceDigest:
|
|
515
|
+
workspaceDigest: workspaceDigest(workspace),
|
|
516
516
|
workspaceRevision: workspace.revision
|
|
517
517
|
};
|
|
518
518
|
nextHotel.generationNo = 1;
|
|
@@ -522,7 +522,7 @@ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
|
|
|
522
522
|
nextHotel.currentGeneration = {
|
|
523
523
|
generationId: `${loaded.hotelRef}:generation:1`,
|
|
524
524
|
source,
|
|
525
|
-
offerSetDigest:
|
|
525
|
+
offerSetDigest: digest(refs),
|
|
526
526
|
orderedOfferRefs: refs,
|
|
527
527
|
evidence: 'complete',
|
|
528
528
|
valid: refs.length > 0
|
|
@@ -531,15 +531,15 @@ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
|
|
|
531
531
|
next.availabilityPhase = 'need_check';
|
|
532
532
|
return next;
|
|
533
533
|
}
|
|
534
|
-
export function
|
|
535
|
-
|
|
534
|
+
export function recordOfferCheckIssued(state, workspace, offerRef, offerVersionRef, actionId = `offer-check-${offerRef}-${state.attempts.length + 1}`) {
|
|
535
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
536
536
|
if (state.terminal) throw new Error('availability_terminal');
|
|
537
537
|
if (!state.recoveryStarted) {
|
|
538
538
|
if (!loadedOfferVersion(workspace, offerRef, offerVersionRef)) throw new Error('availability_offer_not_loaded');
|
|
539
|
-
const started =
|
|
540
|
-
return
|
|
539
|
+
const started = startRecoveryAtOffer(state, workspace, offerRef, actionId);
|
|
540
|
+
return recordOfferCheckIssued(started, workspace, offerRef, offerVersionRef, actionId);
|
|
541
541
|
}
|
|
542
|
-
const decision =
|
|
542
|
+
const decision = canIssueOfferCheck(state, workspace, offerRef, offerVersionRef);
|
|
543
543
|
if (!decision.ok) throw new Error(`availability_${decision.code}`);
|
|
544
544
|
const next = copyState(state);
|
|
545
545
|
const hotel = next.hotels[decision.hotelRef];
|
|
@@ -558,21 +558,21 @@ export function recordOfferCheckIssuedV2(state, workspace, offerRef, offerVersio
|
|
|
558
558
|
next.availabilityPhase = 'waiting_check';
|
|
559
559
|
return next;
|
|
560
560
|
}
|
|
561
|
-
export function
|
|
561
|
+
export function reduceAvailabilityAction(state, workspace, action) {
|
|
562
562
|
if (action.kind === 'offers.query') {
|
|
563
|
-
if (!
|
|
564
|
-
if (!
|
|
565
|
-
if (!
|
|
566
|
-
const criteriaDigest =
|
|
567
|
-
let next =
|
|
568
|
-
if (state.recoveryStarted) next =
|
|
563
|
+
if (!isOfferCriteria(action.input.criteria) && (!state.criteria || !isOfferCriteria(state.criteria))) throw new Error('availability_criteria_required');
|
|
564
|
+
if (!isOfferCriteria(action.input.criteria) && state.recoveryStarted) throw new Error('availability_criteria_required');
|
|
565
|
+
if (!isOfferCriteria(action.input.criteria)) throw new Error('availability_criteria_required');
|
|
566
|
+
const criteriaDigest = digest(action.input.criteria);
|
|
567
|
+
let next = recordObservedOffersQuery(state, action.input.hotelRefs, criteriaDigest);
|
|
568
|
+
if (state.recoveryStarted) next = recordOffersQueryIssued(next, action.input.hotelRefs, workspace, action.actionId, criteriaDigest, action.input.criteria);
|
|
569
569
|
return next;
|
|
570
570
|
}
|
|
571
|
-
if (action.kind === 'offer.check') return
|
|
571
|
+
if (action.kind === 'offer.check') return recordOfferCheckIssued(state, workspace, action.input.offerRef, action.input.offerVersionRef, action.actionId);
|
|
572
572
|
return copyState(state);
|
|
573
573
|
}
|
|
574
|
-
export function
|
|
575
|
-
|
|
574
|
+
export function recordOfferCheckReceipt(state, workspace, receipt, actionId, offerRef, offerVersionRef, expectedRevision) {
|
|
575
|
+
assertWorkspaceLoadedOfferRefsUnique(workspace);
|
|
576
576
|
if (!state.recoveryStarted) return copyState(state);
|
|
577
577
|
const attempt = state.attempts.find((candidate)=>candidate.actionId === actionId);
|
|
578
578
|
if (!attempt) throw new Error('availability_attempt_unknown');
|
|
@@ -642,7 +642,7 @@ export function recordOfferCheckReceiptV2(state, workspace, receipt, actionId, o
|
|
|
642
642
|
...hotel.currentGeneration.orderedOfferRefs
|
|
643
643
|
];
|
|
644
644
|
hotel.currentOfferRefs = [];
|
|
645
|
-
hotel.freshOffersRequired = hotel.checksIssued <
|
|
645
|
+
hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL;
|
|
646
646
|
hotel.status = completeNegative ? 'negative' : 'inconclusive';
|
|
647
647
|
hotel.lastEvidence = completeNegative ? 'unavailable' : 'inconclusive';
|
|
648
648
|
const currentOrdinal = next.hotelRefs.indexOf(attempt.hotelRef);
|
|
@@ -654,24 +654,24 @@ export function recordOfferCheckReceiptV2(state, workspace, receipt, actionId, o
|
|
|
654
654
|
next.availabilityPhase = phaseForActiveHotel(next);
|
|
655
655
|
return withTerminal(next);
|
|
656
656
|
}
|
|
657
|
-
export function
|
|
658
|
-
let next =
|
|
657
|
+
export function reduceAvailabilityReceipt(state, workspace, receipt, action) {
|
|
658
|
+
let next = recordVisibleHotels(state, workspace);
|
|
659
659
|
if (action.kind === 'offers.query' && !state.recoveryStarted) {
|
|
660
|
-
if (!
|
|
661
|
-
next =
|
|
660
|
+
if (!isOfferCriteria(action.input.criteria)) throw new Error('availability_criteria_required');
|
|
661
|
+
next = recordObservedOffersQuery(next, action.input.hotelRefs, digest(action.input.criteria), workspace, receipt, action.actionId, digest(receipt), action.input.criteria);
|
|
662
662
|
} else if (action.kind === 'offers.query' && state.recoveryStarted) {
|
|
663
|
-
if (!
|
|
664
|
-
next =
|
|
663
|
+
if (!isOfferCriteria(action.input.criteria)) throw new Error('availability_criteria_required');
|
|
664
|
+
next = recordOffersGeneration(next, action.input.hotelRefs, workspace, action.actionId, digest(receipt), receipt, state.criteriaDigest ?? '', action.input.criteria);
|
|
665
665
|
} else if (action.kind === 'offer.check') {
|
|
666
666
|
if (action.expectedRevision === undefined) throw new Error('availability_expected_revision_missing');
|
|
667
|
-
next =
|
|
667
|
+
next = recordOfferCheckReceipt(next, workspace, receipt, action.actionId, action.input.offerRef, action.input.offerVersionRef, action.expectedRevision);
|
|
668
668
|
}
|
|
669
669
|
return next;
|
|
670
670
|
}
|
|
671
|
-
export function
|
|
671
|
+
export function availabilityPolicyIsTerminal(state) {
|
|
672
672
|
return Boolean(state.terminal);
|
|
673
673
|
}
|
|
674
|
-
export function
|
|
674
|
+
export function availabilityPolicyResult(state) {
|
|
675
675
|
return state.terminal ? {
|
|
676
676
|
...state.terminal,
|
|
677
677
|
hotelRefs: [
|
|
@@ -679,7 +679,7 @@ export function availabilityPolicyResultV2(state) {
|
|
|
679
679
|
]
|
|
680
680
|
} : undefined;
|
|
681
681
|
}
|
|
682
|
-
export function
|
|
682
|
+
export function validateAvailabilityPolicy(state) {
|
|
683
683
|
try {
|
|
684
684
|
if (!state || typeof state.initialized !== 'boolean' || typeof state.recoveryStarted !== 'boolean') return false;
|
|
685
685
|
if (state.recoveryId !== undefined && (typeof state.recoveryId !== 'string' || !state.recoveryId)) return false;
|
|
@@ -692,7 +692,7 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
692
692
|
'waiting_check',
|
|
693
693
|
'terminal'
|
|
694
694
|
].includes(state.availabilityPhase)) return false;
|
|
695
|
-
if (!Number.isSafeInteger(state.activeHotelOrdinal) || !Array.isArray(state.hotelRefs) || state.hotelRefs.length >
|
|
695
|
+
if (!Number.isSafeInteger(state.activeHotelOrdinal) || !Array.isArray(state.hotelRefs) || state.hotelRefs.length > MAX_HOTELS_PER_TASK) return false;
|
|
696
696
|
if (state.hotelRefs.some((ref)=>typeof ref !== 'string' || !ref) || new Set(state.hotelRefs).size !== state.hotelRefs.length) return false;
|
|
697
697
|
if (state.hotelRefs.length && (state.activeHotelOrdinal < 0 || state.activeHotelOrdinal >= state.hotelRefs.length) || !state.hotelRefs.length && state.activeHotelOrdinal !== 0) return false;
|
|
698
698
|
if (!state.hotels || typeof state.hotels !== 'object' || Object.keys(state.hotels).sort().join('\0') !== [
|
|
@@ -702,7 +702,7 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
702
702
|
if (!state.recoveryStarted) {
|
|
703
703
|
if (state.recoveryId !== undefined || state.candidateSetDigest !== undefined || state.hotelRefs.length || Object.keys(state.hotels).length || state.attempts.length || state.queryReservations.length || state.terminal !== undefined) return false;
|
|
704
704
|
if (state.criteria !== undefined || state.criteriaDigest !== undefined) {
|
|
705
|
-
if (!
|
|
705
|
+
if (!isOfferCriteria(state.criteria) || !state.criteriaDigest || state.criteriaDigest !== digest(state.criteria)) return false;
|
|
706
706
|
}
|
|
707
707
|
if (state.lastQueryCriteriaDigest !== undefined && state.lastQueryCriteriaDigest !== state.criteriaDigest) return false;
|
|
708
708
|
if (state.lastQueryHotelRefs !== undefined && (!Array.isArray(state.lastQueryHotelRefs) || new Set(state.lastQueryHotelRefs).size !== state.lastQueryHotelRefs.length || state.lastQueryHotelRefs.some((ref)=>typeof ref !== 'string' || !ref))) return false;
|
|
@@ -711,8 +711,8 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
711
711
|
if (state.lastQuerySourceReceiptDigest !== undefined && state.lastQuerySourceActionId === undefined) return false;
|
|
712
712
|
return state.initialized === false && state.availabilityPhase === 'need_offers';
|
|
713
713
|
}
|
|
714
|
-
if (!state.initialized || !state.recoveryId || !state.candidateSetDigest || state.candidateSetDigest !==
|
|
715
|
-
const criteriaBound =
|
|
714
|
+
if (!state.initialized || !state.recoveryId || !state.candidateSetDigest || state.candidateSetDigest !== digest(state.hotelRefs)) return false;
|
|
715
|
+
const criteriaBound = isOfferCriteria(state.criteria) && Boolean(state.criteriaDigest) && state.criteriaDigest === digest(state.criteria);
|
|
716
716
|
const manualCheckWorkspaceSnapshot = state.attempts.length > 0 && state.attempts.every((attempt)=>state.hotels[attempt.hotelRef]?.currentGeneration?.source.kind === 'workspace_snapshot');
|
|
717
717
|
const manualCheckMayBindCriteria = !state.criteria && !state.criteriaDigest && state.queryReservations.length === 0 && manualCheckWorkspaceSnapshot && ([
|
|
718
718
|
'waiting_check',
|
|
@@ -748,20 +748,20 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
748
748
|
hotel.generation,
|
|
749
749
|
hotel.checkCount
|
|
750
750
|
].every(Number.isSafeInteger)) return false;
|
|
751
|
-
if (hotel.checksIssued < 0 || hotel.checksIssued >
|
|
751
|
+
if (hotel.checksIssued < 0 || hotel.checksIssued > MAX_OFFER_CHECKS_PER_HOTEL || hotel.offerQueriesIssued < 0 || hotel.offerQueriesIssued > MAX_OFFER_QUERIES_PER_HOTEL || hotel.generationNo < 0 || hotel.generation !== hotel.generationNo || hotel.checkCount !== hotel.checksIssued) return false;
|
|
752
752
|
if (typeof hotel.freshOffersRequired !== 'boolean' || !Array.isArray(hotel.currentOfferRefs) || !Array.isArray(hotel.invalidatedOfferRefs) || !Array.isArray(hotel.tombstonedOfferRefs) || !Array.isArray(hotel.tombstonedOfferVersionRefs)) return false;
|
|
753
753
|
if (hotel.currentOfferRefs.some((offer)=>typeof offer !== 'string' || !offer) || hotel.invalidatedOfferRefs.some((offer)=>typeof offer !== 'string' || !offer) || hotel.tombstonedOfferRefs.some((offer)=>typeof offer !== 'string' || !offer) || hotel.tombstonedOfferVersionRefs.some((offer)=>typeof offer !== 'string' || !offer)) return false;
|
|
754
|
-
if (new Set(hotel.currentOfferRefs).size !== hotel.currentOfferRefs.length || new Set(hotel.invalidatedOfferRefs).size !== hotel.invalidatedOfferRefs.length || new Set(hotel.tombstonedOfferRefs).size !== hotel.tombstonedOfferRefs.length || new Set(hotel.tombstonedOfferVersionRefs).size !== hotel.tombstonedOfferVersionRefs.length || hotel.currentOfferRefs.length >
|
|
754
|
+
if (new Set(hotel.currentOfferRefs).size !== hotel.currentOfferRefs.length || new Set(hotel.invalidatedOfferRefs).size !== hotel.invalidatedOfferRefs.length || new Set(hotel.tombstonedOfferRefs).size !== hotel.tombstonedOfferRefs.length || new Set(hotel.tombstonedOfferVersionRefs).size !== hotel.tombstonedOfferVersionRefs.length || hotel.currentOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || hotel.invalidatedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || hotel.currentOfferRefs.some((offer)=>hotel.invalidatedOfferRefs.includes(offer) || hotel.tombstonedOfferRefs.includes(offer))) return false;
|
|
755
755
|
const generation = hotel.currentGeneration;
|
|
756
756
|
if (hotel.generationNo === 0) {
|
|
757
757
|
if (generation !== undefined || hotel.currentOfferRefs.length || hotel.invalidatedOfferRefs.length || hotel.tombstonedOfferRefs.length || hotel.tombstonedOfferVersionRefs.length || hotel.status !== 'unvisited' || hotel.lastEvidence !== 'none' || hotel.checksIssued !== 0 || hotel.offerQueriesIssued !== 0 || hotel.freshOffersRequired) return false;
|
|
758
758
|
continue;
|
|
759
759
|
}
|
|
760
|
-
if (!generation || generation.generationId !== `${ref}:generation:${hotel.generationNo}` || !/^[0-9a-f]{64}$/.test(generation.offerSetDigest) || generation.offerSetDigest !==
|
|
760
|
+
if (!generation || generation.generationId !== `${ref}:generation:${hotel.generationNo}` || !/^[0-9a-f]{64}$/.test(generation.offerSetDigest) || generation.offerSetDigest !== digest(generation.orderedOfferRefs) || !Array.isArray(generation.orderedOfferRefs) || generation.orderedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION || generation.orderedOfferRefs.some((offer)=>typeof offer !== 'string' || !offer) || new Set(generation.orderedOfferRefs).size !== generation.orderedOfferRefs.length || ![
|
|
761
761
|
'complete',
|
|
762
762
|
'partial'
|
|
763
763
|
].includes(generation.evidence) || typeof generation.valid !== 'boolean') return false;
|
|
764
|
-
if (generation.offerSetDigest !==
|
|
764
|
+
if (generation.offerSetDigest !== digest(generation.orderedOfferRefs) || hotel.currentOfferRefs.some((offer)=>!generation.orderedOfferRefs.includes(offer)) || hotel.invalidatedOfferRefs.some((offer)=>!generation.orderedOfferRefs.includes(offer))) return false;
|
|
765
765
|
const source = generation.source;
|
|
766
766
|
if (!source || ![
|
|
767
767
|
'query_receipt',
|
|
@@ -770,7 +770,7 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
770
770
|
if (source.kind === 'query_receipt' && (typeof source.actionId !== 'string' || !source.actionId || !/^[0-9a-f]{64}$/.test(source.receiptDigest))) return false;
|
|
771
771
|
if (source.kind === 'workspace_snapshot' && (typeof source.workspaceDigest !== 'string' || !/^[0-9a-f]{64}$/.test(source.workspaceDigest))) return false;
|
|
772
772
|
if (source.kind === 'query_receipt' && state.lastQuerySourceActionId === source.actionId && (state.lastQuerySourceReceiptDigest !== source.receiptDigest || state.lastQueryWorkspaceRevision !== source.workspaceRevision)) return false;
|
|
773
|
-
if (hotel.freshOffersRequired && (hotel.currentOfferRefs.length !== 0 || hotel.checksIssued >=
|
|
773
|
+
if (hotel.freshOffersRequired && (hotel.currentOfferRefs.length !== 0 || hotel.checksIssued >= MAX_OFFER_CHECKS_PER_HOTEL || hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL)) return false;
|
|
774
774
|
if (hotel.status === 'unvisited' || hotel.status === 'negative') {
|
|
775
775
|
if (hotel.status === 'unvisited' || hotel.lastEvidence !== 'unavailable' || hotel.currentOfferRefs.length || generation.valid) return false;
|
|
776
776
|
}
|
|
@@ -798,7 +798,7 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
798
798
|
if (new Set(state.queryReservations.map((reservation)=>reservation.actionId)).size !== state.queryReservations.length) return false;
|
|
799
799
|
const reservedHotels = new Set();
|
|
800
800
|
for (const reservation of state.queryReservations){
|
|
801
|
-
if (typeof reservation.actionId !== 'string' || !reservation.actionId || !Array.isArray(reservation.hotelRefs) || !reservation.hotelRefs.length || reservation.hotelRefs.length >
|
|
801
|
+
if (typeof reservation.actionId !== 'string' || !reservation.actionId || !Array.isArray(reservation.hotelRefs) || !reservation.hotelRefs.length || reservation.hotelRefs.length > MAX_HOTELS_PER_TASK || new Set(reservation.hotelRefs).size !== reservation.hotelRefs.length || reservation.hotelRefs.some((ref)=>!state.hotels[ref]) || !Number.isSafeInteger(reservation.workspaceRevision) || reservation.workspaceRevision < 0 || reservation.hotelRefs.some((ref)=>reservedHotels.has(ref))) return false;
|
|
802
802
|
reservation.hotelRefs.forEach((ref)=>reservedHotels.add(ref));
|
|
803
803
|
}
|
|
804
804
|
if (state.availabilityPhase === 'waiting_offers' && state.queryReservations.length === 0) return false;
|
|
@@ -812,7 +812,7 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
812
812
|
if (state.availabilityPhase !== 'terminal' && state.terminal) return false;
|
|
813
813
|
const recomputedTerminal = terminalFor(state);
|
|
814
814
|
if (state.terminal) {
|
|
815
|
-
if (!recomputedTerminal ||
|
|
815
|
+
if (!recomputedTerminal || digest(state.terminal) !== digest(recomputedTerminal)) return false;
|
|
816
816
|
} else if (recomputedTerminal && ![
|
|
817
817
|
'waiting_offers',
|
|
818
818
|
'waiting_check'
|
|
@@ -822,9 +822,9 @@ export function validateAvailabilityPolicyV2(state) {
|
|
|
822
822
|
return false;
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
|
-
export function
|
|
825
|
+
export function availabilityPolicyActionIsRelevant(action) {
|
|
826
826
|
return action.kind === 'offers.query' || action.kind === 'offer.check';
|
|
827
827
|
}
|
|
828
828
|
|
|
829
829
|
|
|
830
|
-
//# sourceURL=ts/src/booking-surface/availability-policy
|
|
830
|
+
//# sourceURL=ts/src/booking-surface/availability-policy.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime projection of the canonical booking.surface
|
|
2
|
+
* Runtime projection of the canonical booking.surface JSON Schema.
|
|
3
3
|
*
|
|
4
4
|
* dsh tools need self-contained JSON Schemas, while the canonical document is
|
|
5
5
|
* organized with local $refs. This helper derives each action branch directly
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { readFileSync } from 'node:fs'
|
|
11
11
|
|
|
12
|
-
const schemaUrl = new URL('../../../schemas/booking.surface.
|
|
13
|
-
export const
|
|
14
|
-
const schemaV2Url = new URL('../../../schemas/booking.surface.v2.schema.json', import.meta.url)
|
|
15
|
-
export const canonicalBookingSurfaceSchemaV2 = JSON.parse(readFileSync(schemaV2Url, 'utf8'))
|
|
12
|
+
const schemaUrl = new URL('../../../schemas/booking.surface.schema.json', import.meta.url)
|
|
13
|
+
export const canonicalBookingSurfaceSchema = JSON.parse(readFileSync(schemaUrl, 'utf8'))
|
|
16
14
|
|
|
17
15
|
function clone(value) {
|
|
18
16
|
return JSON.parse(JSON.stringify(value))
|
|
@@ -20,14 +18,11 @@ function clone(value) {
|
|
|
20
18
|
|
|
21
19
|
function definitionFromRef(ref, schema) {
|
|
22
20
|
const prefix = '#/$defs/'
|
|
23
|
-
if (typeof ref !== 'string') {
|
|
21
|
+
if (typeof ref !== 'string' || !ref.startsWith(prefix)) {
|
|
24
22
|
throw new Error(`booking_schema_external_ref_forbidden:${String(ref)}`)
|
|
25
23
|
}
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
if (!definitionSchema) throw new Error(`booking_schema_external_ref_forbidden:${String(ref)}`)
|
|
29
|
-
const name = ref.startsWith(prefix) ? ref.slice(prefix.length) : ref.slice(externalV1.length)
|
|
30
|
-
const definition = definitionSchema.$defs?.[name]
|
|
24
|
+
const name = ref.slice(prefix.length)
|
|
25
|
+
const definition = schema.$defs?.[name]
|
|
31
26
|
if (!definition) throw new Error(`booking_schema_missing_definition:${name}`)
|
|
32
27
|
return definition
|
|
33
28
|
}
|
|
@@ -38,9 +33,8 @@ function inlineLocalRefs(value, schema, stack = []) {
|
|
|
38
33
|
if (typeof value.$ref === 'string') {
|
|
39
34
|
if (stack.includes(value.$ref)) throw new Error(`booking_schema_recursive_ref:${value.$ref}`)
|
|
40
35
|
const { $ref, ...siblings } = value
|
|
41
|
-
const definitionSchema = typeof $ref === 'string' && $ref.startsWith('https://gotry.dev/schemas/booking.surface.v1.schema.json#/$defs/') ? canonicalBookingSurfaceSchemaV1 : schema
|
|
42
36
|
return {
|
|
43
|
-
...inlineLocalRefs(definitionFromRef($ref, schema),
|
|
37
|
+
...inlineLocalRefs(definitionFromRef($ref, schema), schema, [...stack, $ref]),
|
|
44
38
|
...inlineLocalRefs(siblings, schema, stack),
|
|
45
39
|
}
|
|
46
40
|
}
|
|
@@ -73,11 +67,7 @@ function canonicalActionSchemaForKind(kind, schema, definitionName) {
|
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
export function canonicalBookingActionSchemaForKind(kind) {
|
|
76
|
-
return canonicalActionSchemaForKind(kind,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function canonicalBookingActionSchemaForKindV2(kind) {
|
|
80
|
-
return canonicalActionSchemaForKind(kind, canonicalBookingSurfaceSchemaV2, 'Action')
|
|
70
|
+
return canonicalActionSchemaForKind(kind, canonicalBookingSurfaceSchema, 'Action')
|
|
81
71
|
}
|
|
82
72
|
|
|
83
73
|
const DSH_JSON_SCHEMA_KEYS = new Set([
|
|
@@ -107,7 +97,3 @@ function projectDshSchema(value) {
|
|
|
107
97
|
export function dshBookingActionSchemaForKind(kind) {
|
|
108
98
|
return projectDshSchema(canonicalBookingActionSchemaForKind(kind))
|
|
109
99
|
}
|
|
110
|
-
|
|
111
|
-
export function dshBookingActionSchemaForKindV2(kind) {
|
|
112
|
-
return projectDshSchema(canonicalBookingActionSchemaForKindV2(kind))
|
|
113
|
-
}
|