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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/README.md +161 -116
  2. package/README.zh-CN.md +173 -144
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +113 -66
  5. package/bin/gotry-inner.js +436 -70
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +5 -1
  10. package/dist/capabilities/agent-reach-deep.js +1 -1
  11. package/dist/capabilities/agent-reach.js +1 -1
  12. package/dist/capabilities/anything.js +1 -1
  13. package/dist/capabilities/artifacts.js +1 -1
  14. package/dist/capabilities/effect.js +1 -1
  15. package/dist/capabilities/fact-log.js +1 -1
  16. package/dist/capabilities/flyai.js +20 -6
  17. package/dist/capabilities/hbcli.js +1 -1
  18. package/dist/capabilities/incident-log.js +1 -1
  19. package/dist/capabilities/model-override.js +18 -0
  20. package/dist/capabilities/opensky.js +1 -1
  21. package/dist/capabilities/resilience.js +1 -1
  22. package/dist/capabilities/session/action-cache.js +1 -1
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  24. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  25. package/dist/capabilities/session/benchmark.js +1 -1
  26. package/dist/capabilities/session/extension-bridge.js +19 -6
  27. package/dist/capabilities/session/extension-channel.js +3 -3
  28. package/dist/capabilities/session/extension-distribution.js +234 -0
  29. package/dist/capabilities/session/extract.js +1 -1
  30. package/dist/capabilities/session/golden-score.js +92 -0
  31. package/dist/capabilities/session/health-watch.js +1 -1
  32. package/dist/capabilities/session/read-guard.js +1 -1
  33. package/dist/capabilities/session/static-flight-golden.js +137 -0
  34. package/dist/capabilities/session/transport.js +1 -1
  35. package/dist/capabilities/session/wizard.js +21 -251
  36. package/dist/capabilities/session-consent.js +1 -1
  37. package/dist/capabilities/session-login.js +11 -5
  38. package/dist/capabilities/session-search.js +46 -4
  39. package/dist/capabilities/weather.js +168 -46
  40. package/dist/data/session-golden-20.json +25 -0
  41. package/dist/data/sf-golden-manifest.json +102 -0
  42. package/dist/data/sf-static-routes.json +91 -0
  43. package/dist/scripts/action-cache-tests.js +1 -1
  44. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  45. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  46. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  47. package/dist/scripts/agent-reach-tests.js +1 -1
  48. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  49. package/dist/scripts/anything-tests.js +1 -1
  50. package/dist/scripts/async-collect.js +1 -1
  51. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  52. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  53. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  54. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  55. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  56. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  57. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  58. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  59. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  60. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  61. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  62. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  63. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  64. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  65. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  66. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  67. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  68. package/dist/scripts/booking-saga-tests.js +1 -1
  69. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  70. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  71. package/dist/scripts/bootstrap-tests.js +45 -39
  72. package/dist/scripts/build-changelog.js +1 -1
  73. package/dist/scripts/changelog-tests.js +1 -1
  74. package/dist/scripts/companion-tests.js +1 -1
  75. package/dist/scripts/diff-test.js +1 -1
  76. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  77. package/dist/scripts/dsh-runtime-closure.js +150 -0
  78. package/dist/scripts/effect-tests.js +1 -1
  79. package/dist/scripts/engine-run.js +1 -1
  80. package/dist/scripts/engine-tests.js +1 -1
  81. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  82. package/dist/scripts/evaluation-contract-tests.js +574 -0
  83. package/dist/scripts/extension-distribution-cli.js +35 -0
  84. package/dist/scripts/extension-distribution-tests.js +341 -0
  85. package/dist/scripts/extension-tests.js +128 -23
  86. package/dist/scripts/fact-gate-tests.js +1 -1
  87. package/dist/scripts/flyai-tests.js +1 -1
  88. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  89. package/dist/scripts/hbcli-tests.js +1 -1
  90. package/dist/scripts/health-watch-cli.js +1 -1
  91. package/dist/scripts/i18n-tests.js +1 -1
  92. package/dist/scripts/incident-tests.js +1 -1
  93. package/dist/scripts/journey-tests.js +1 -1
  94. package/dist/scripts/ledger-tests.js +1 -1
  95. package/dist/scripts/ledger-workflow-crash.js +1 -1
  96. package/dist/scripts/memory-capture-tests.js +1 -1
  97. package/dist/scripts/memory-decay-tests.js +1 -1
  98. package/dist/scripts/memory-metrics.js +1 -1
  99. package/dist/scripts/memory-value-report.js +1 -1
  100. package/dist/scripts/model-override-e2e.js +176 -0
  101. package/dist/scripts/nightly-evidence-tests.js +1 -1
  102. package/dist/scripts/nightly-evidence.js +1 -1
  103. package/dist/scripts/nudge-digest.js +1 -1
  104. package/dist/scripts/onboarding-tests.js +21 -53
  105. package/dist/scripts/opensky-check.js +1 -1
  106. package/dist/scripts/opensky-tests.js +1 -1
  107. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  108. package/dist/scripts/price-drift-tests.js +1 -1
  109. package/dist/scripts/price-drift-watch.js +1 -1
  110. package/dist/scripts/probe-poi-tests.js +1 -1
  111. package/dist/scripts/product-metrics.js +1 -1
  112. package/dist/scripts/publish-preverify.js +40 -4
  113. package/dist/scripts/realtime-pricing-tests.js +1 -1
  114. package/dist/scripts/replay-async.js +1 -1
  115. package/dist/scripts/replay-real.js +1 -1
  116. package/dist/scripts/replay.js +1 -1
  117. package/dist/scripts/session-attach-diagnose.js +1 -1
  118. package/dist/scripts/session-attach-poc.js +1 -1
  119. package/dist/scripts/session-benchmark.js +1 -1
  120. package/dist/scripts/session-extract-tests.js +1 -1
  121. package/dist/scripts/session-login.js +1 -1
  122. package/dist/scripts/session-tests.js +70 -20
  123. package/dist/scripts/sf-live-benchmark.js +338 -0
  124. package/dist/scripts/sf-live-cli-tests.js +21 -0
  125. package/dist/scripts/sf-soft-score-tests.js +108 -0
  126. package/dist/scripts/sf-summary.js +93 -0
  127. package/dist/scripts/skeleton-check.js +1 -1
  128. package/dist/scripts/skeleton-integration-test.js +1 -1
  129. package/dist/scripts/skills-contract-tests.js +1 -1
  130. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  131. package/dist/scripts/smoke.js +79 -33
  132. package/dist/scripts/state-cli-tests.js +1 -1
  133. package/dist/scripts/state-cli.js +1 -1
  134. package/dist/scripts/static-golden-tests.js +299 -0
  135. package/dist/scripts/time-eval-tests.js +1 -1
  136. package/dist/scripts/travel-timeline-tests.js +1 -1
  137. package/dist/scripts/unified-tests.js +1 -1
  138. package/dist/scripts/weather-tests.js +694 -44
  139. package/dist/scripts/z3-race-tests.js +1 -1
  140. package/dist/src/artifact-gate.js +1 -1
  141. package/dist/src/benchmark-agent-conformance.js +370 -0
  142. package/dist/src/benchmark-environment-bridge.js +384 -0
  143. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  144. package/dist/src/benchmark-tool-isolation.js +124 -0
  145. package/dist/src/bookable-facts.js +1 -1
  146. package/dist/src/booking-saga.js +1 -1
  147. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  148. package/dist/src/booking-surface/canonical-schema.js +113 -0
  149. package/dist/src/booking-surface/contracts-v2.js +89 -0
  150. package/dist/src/booking-surface/contracts.js +46 -0
  151. package/dist/src/booking-surface/dsh-planner.js +453 -0
  152. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  153. package/dist/src/booking-surface/error-codes.js +94 -0
  154. package/dist/src/booking-surface/index.js +15 -0
  155. package/dist/src/booking-surface/profile.js +68 -0
  156. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  157. package/dist/src/booking-surface/runtime.js +351 -0
  158. package/dist/src/booking-surface/server-v2.js +334 -0
  159. package/dist/src/booking-surface/server.js +302 -0
  160. package/dist/src/booking-surface/startup.js +159 -0
  161. package/dist/src/booking-surface/validation-v2.js +319 -0
  162. package/dist/src/booking-surface/validation.js +809 -0
  163. package/dist/src/bridge.js +1 -1
  164. package/dist/src/companions.js +1 -1
  165. package/dist/src/contracts.js +1 -1
  166. package/dist/src/dsh-llm.js +1 -1
  167. package/dist/src/engine.js +1 -1
  168. package/dist/src/evaluation-cadence.js +234 -0
  169. package/dist/src/evaluation-contracts.js +906 -0
  170. package/dist/src/i18n.js +1 -1
  171. package/dist/src/index.js +50 -19
  172. package/dist/src/journey.js +1 -1
  173. package/dist/src/loop.js +1 -1
  174. package/dist/src/memory-capture.js +1 -1
  175. package/dist/src/memory-decay.js +1 -1
  176. package/dist/src/memory-utility.js +1 -1
  177. package/dist/src/mock-llm.js +1 -1
  178. package/dist/src/model.js +1 -1
  179. package/dist/src/realtime-pricing.js +1 -1
  180. package/dist/src/slot-spec.js +1 -1
  181. package/dist/src/state-ledger.js +2 -1
  182. package/dist/src/time-anchor.js +1 -1
  183. package/dist/src/tool-budget.js +136 -0
  184. package/dist/src/tool-packet.js +1 -1
  185. package/dist/src/travel-slots.js +1 -1
  186. package/dist/src/travel-timeline.js +1 -1
  187. package/dist/src/unified.js +1 -1
  188. package/dist/src/wish-pool.js +1 -1
  189. package/dist/src/z3-shared.js +1 -1
  190. package/extension/README.md +31 -7
  191. package/package.json +286 -11
  192. package/schemas/booking.surface.v1.schema.json +927 -0
  193. package/schemas/booking.surface.v2.schema.json +61 -0
  194. package/ts/capabilities/flyai.ts +16 -3
  195. package/ts/capabilities/session/extension-bridge.ts +37 -14
  196. package/ts/capabilities/session/extension-channel.ts +6 -3
  197. package/ts/capabilities/session/extension-distribution.ts +264 -0
  198. package/ts/capabilities/session/golden-score.ts +139 -0
  199. package/ts/capabilities/session/health-watch.ts +1 -1
  200. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  201. package/ts/capabilities/session/wizard.ts +34 -176
  202. package/ts/capabilities/session-login.ts +15 -5
  203. package/ts/capabilities/session-search.ts +40 -3
  204. package/ts/capabilities/weather.ts +141 -52
  205. package/ts/package.json +3 -3
  206. package/ts/src/benchmark-agent-conformance.ts +448 -0
  207. package/ts/src/benchmark-environment-bridge.ts +348 -0
  208. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  209. package/ts/src/benchmark-tool-isolation.ts +166 -0
  210. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  211. package/ts/src/booking-surface/canonical-schema.js +113 -0
  212. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  213. package/ts/src/booking-surface/contracts.ts +380 -0
  214. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  215. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  216. package/ts/src/booking-surface/error-codes.ts +101 -0
  217. package/ts/src/booking-surface/index.ts +12 -0
  218. package/ts/src/booking-surface/profile.ts +42 -0
  219. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  220. package/ts/src/booking-surface/runtime.ts +483 -0
  221. package/ts/src/booking-surface/server-v2.ts +247 -0
  222. package/ts/src/booking-surface/server.ts +324 -0
  223. package/ts/src/booking-surface/startup.ts +196 -0
  224. package/ts/src/booking-surface/validation-v2.ts +205 -0
  225. package/ts/src/booking-surface/validation.ts +453 -0
  226. package/ts/src/index.ts +64 -11
  227. package/ts/src/state-ledger.ts +1 -0
  228. package/ts/src/tool-budget.ts +165 -0
  229. package/dist/scripts/wizard-bootstrap.js +0 -32
@@ -0,0 +1,830 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const MAX_HOTELS_PER_TASK_V2 = 5;
3
+ export const MAX_OFFERS_PER_HOTEL_GENERATION_V2 = 3;
4
+ export const MAX_OFFER_CHECKS_PER_HOTEL_V2 = 2;
5
+ export const MAX_OFFER_QUERIES_PER_HOTEL_V2 = 2;
6
+ function unique(values) {
7
+ return [
8
+ ...new Set(values)
9
+ ];
10
+ }
11
+ export function assertWorkspaceLoadedOfferRefsUniqueV2(workspace) {
12
+ if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer)=>offer.offerRef)).size) throw new Error('availability_duplicate_offer_ref');
13
+ if (workspace.loadedOffers.length !== new Set(workspace.loadedOffers.map((offer)=>offer.offerVersionRef)).size) throw new Error('availability_duplicate_offer_version_ref');
14
+ }
15
+ function stable(value) {
16
+ if (Array.isArray(value)) return value.map(stable);
17
+ if (!value || typeof value !== 'object') return value;
18
+ return Object.fromEntries(Object.keys(value).sort().map((key)=>[
19
+ key,
20
+ stable(value[key])
21
+ ]));
22
+ }
23
+ export function digestV2(value) {
24
+ return createHash('sha256').update(JSON.stringify(stable(value))).digest('hex');
25
+ }
26
+ function workspaceDigestV2(workspace) {
27
+ const { contextRef: _context, ...bound } = workspace;
28
+ return digestV2(bound);
29
+ }
30
+ function offerRefsForHotel(workspace, hotelRef, selectedOfferRef) {
31
+ const refs = unique(workspace.loadedOffers.filter((offer)=>offer.hotelRef === hotelRef).map((offer)=>offer.offerRef));
32
+ if (selectedOfferRef) return unique([
33
+ selectedOfferRef,
34
+ ...refs
35
+ ]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION_V2);
36
+ if (refs.length > MAX_OFFERS_PER_HOTEL_GENERATION_V2) throw new Error('availability_offer_limit_exceeded');
37
+ return refs;
38
+ }
39
+ function loadedOfferVersion(workspace, offerRef, offerVersionRef) {
40
+ return workspace.loadedOffers.find((offer)=>offer.offerRef === offerRef && offer.offerVersionRef === offerVersionRef);
41
+ }
42
+ function legalOfferRefsForHotel(workspace, hotel, refs) {
43
+ const legal = refs.filter((ref)=>{
44
+ if (hotel.tombstonedOfferRefs.includes(ref)) return false;
45
+ const loaded = workspace.loadedOffers.find((offer)=>offer.offerRef === ref && offer.hotelRef === hotel.hotelRef);
46
+ return Boolean(loaded && !hotel.tombstonedOfferVersionRefs.includes(loaded.offerVersionRef));
47
+ });
48
+ if (legal.length > MAX_OFFERS_PER_HOTEL_GENERATION_V2) throw new Error('availability_offer_limit_exceeded');
49
+ return unique(legal);
50
+ }
51
+ function copyHotel(hotel) {
52
+ return {
53
+ ...hotel,
54
+ currentOfferRefs: [
55
+ ...hotel.currentOfferRefs
56
+ ],
57
+ invalidatedOfferRefs: [
58
+ ...hotel.invalidatedOfferRefs
59
+ ],
60
+ tombstonedOfferRefs: [
61
+ ...hotel.tombstonedOfferRefs
62
+ ],
63
+ tombstonedOfferVersionRefs: [
64
+ ...hotel.tombstonedOfferVersionRefs
65
+ ],
66
+ ...hotel.currentGeneration ? {
67
+ currentGeneration: {
68
+ ...hotel.currentGeneration,
69
+ orderedOfferRefs: [
70
+ ...hotel.currentGeneration.orderedOfferRefs
71
+ ]
72
+ }
73
+ } : {}
74
+ };
75
+ }
76
+ function copyState(state) {
77
+ return {
78
+ ...state,
79
+ ...state.criteria ? {
80
+ criteria: structuredClone(state.criteria)
81
+ } : {},
82
+ hotelRefs: [
83
+ ...state.hotelRefs
84
+ ],
85
+ hotels: Object.fromEntries(Object.entries(state.hotels).map(([ref, hotel])=>[
86
+ ref,
87
+ copyHotel(hotel)
88
+ ])),
89
+ attempts: state.attempts.map((attempt)=>({
90
+ ...attempt
91
+ })),
92
+ queryReservations: state.queryReservations.map((query)=>({
93
+ ...query,
94
+ hotelRefs: [
95
+ ...query.hotelRefs
96
+ ]
97
+ })),
98
+ ...state.lastQueryHotelRefs ? {
99
+ lastQueryHotelRefs: [
100
+ ...state.lastQueryHotelRefs
101
+ ]
102
+ } : {},
103
+ ...state.lastQueryOfferHotels ? {
104
+ lastQueryOfferHotels: {
105
+ ...state.lastQueryOfferHotels
106
+ }
107
+ } : {},
108
+ ...state.terminal ? {
109
+ terminal: {
110
+ ...state.terminal,
111
+ hotelRefs: [
112
+ ...state.terminal.hotelRefs
113
+ ]
114
+ }
115
+ } : {}
116
+ };
117
+ }
118
+ function isPlainRecord(value) {
119
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
120
+ }
121
+ function isCriterion(value, valueCheck) {
122
+ return isPlainRecord(value) && Object.keys(value).length === 2 && (value.strength === 'must' || value.strength === 'prefer') && valueCheck(value.value);
123
+ }
124
+ function isMoney(value) {
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
+ }
127
+ function isOfferCriteriaV2(value) {
128
+ if (!isPlainRecord(value)) return false;
129
+ const allowed = [
130
+ 'roomType',
131
+ 'bedType',
132
+ 'meals',
133
+ 'freeCancellation',
134
+ 'freeCancellationUntil',
135
+ 'totalPriceMax',
136
+ 'roomsAvailableMin',
137
+ 'payAtProperty',
138
+ 'mobileRate',
139
+ 'targetCount',
140
+ 'sort'
141
+ ];
142
+ if (Object.keys(value).some((key)=>!allowed.includes(key))) return false;
143
+ const strings = (candidate)=>Array.isArray(candidate) && candidate.every((item)=>typeof item === 'string' && Boolean(item));
144
+ if (value.roomType !== undefined && !isCriterion(value.roomType, strings)) return false;
145
+ if (value.bedType !== undefined && !isCriterion(value.bedType, strings)) return false;
146
+ if (value.meals !== undefined && !isCriterion(value.meals, strings)) return false;
147
+ if (value.freeCancellation !== undefined && !isCriterion(value.freeCancellation, (candidate)=>typeof candidate === 'boolean')) return false;
148
+ if (value.freeCancellationUntil !== undefined && !isCriterion(value.freeCancellationUntil, (candidate)=>typeof candidate === 'string' && Boolean(candidate))) return false;
149
+ if (value.totalPriceMax !== undefined && !isCriterion(value.totalPriceMax, isMoney)) return false;
150
+ if (value.roomsAvailableMin !== undefined && !isCriterion(value.roomsAvailableMin, (candidate)=>Number.isSafeInteger(candidate) && candidate >= 0)) return false;
151
+ if (value.payAtProperty !== undefined && !isCriterion(value.payAtProperty, (candidate)=>typeof candidate === 'boolean')) return false;
152
+ if (value.mobileRate !== undefined && !isCriterion(value.mobileRate, (candidate)=>typeof candidate === 'boolean')) return false;
153
+ if (value.targetCount !== undefined && (!Number.isSafeInteger(value.targetCount) || value.targetCount < 0)) return false;
154
+ if (value.sort !== undefined && ![
155
+ 'best_match',
156
+ 'total_price_asc',
157
+ 'cancellation_latest'
158
+ ].includes(value.sort)) return false;
159
+ return true;
160
+ }
161
+ function bindCriteria(next, criteriaDigest, criteria, required = false) {
162
+ if (criteria !== undefined) {
163
+ if (!isOfferCriteriaV2(criteria) || digestV2(criteria) !== criteriaDigest) throw new Error('availability_criteria_invalid');
164
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest) throw new Error('availability_criteria_changed');
165
+ if (next.criteria && digestV2(next.criteria) !== criteriaDigest) throw new Error('availability_criteria_changed');
166
+ next.criteria = structuredClone(criteria);
167
+ next.criteriaDigest = criteriaDigest;
168
+ } else if (required && !next.criteria) throw new Error('availability_criteria_required');
169
+ else if (next.criteria && next.criteriaDigest !== digestV2(next.criteria)) throw new Error('availability_criteria_mismatch');
170
+ else if (!next.criteria && criteriaDigest) {
171
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest) throw new Error('availability_criteria_changed');
172
+ next.criteriaDigest = criteriaDigest;
173
+ }
174
+ if (next.criteriaDigest !== undefined && next.criteriaDigest !== criteriaDigest && criteriaDigest) throw new Error('availability_criteria_changed');
175
+ }
176
+ function terminalFor(state) {
177
+ if (!state.recoveryStarted || !state.hotelRefs.length) return undefined;
178
+ const hotels = state.hotelRefs.map((ref)=>state.hotels[ref]);
179
+ if (hotels.some((hotel)=>hotel.status === 'confirmed')) return {
180
+ code: 'availability_confirmed',
181
+ hotelRefs: [
182
+ ...state.hotelRefs
183
+ ],
184
+ reason: 'confirmed',
185
+ evidence: 'conclusive'
186
+ };
187
+ if (hotels.some((hotel)=>hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL_V2 && (hotel.status === 'active' || hotel.status === 'unvisited' || hotel.freshOffersRequired && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL_V2))) return undefined;
188
+ const conclusive = hotels.every((hotel)=>hotel.status === 'negative');
189
+ return {
190
+ code: conclusive ? 'availability_exhausted_complete' : 'availability_exhausted_inconclusive',
191
+ hotelRefs: [
192
+ ...state.hotelRefs
193
+ ],
194
+ reason: hotels.every((hotel)=>!hotel.currentOfferRefs.length) ? 'no_current_offers' : 'check_limit_reached',
195
+ evidence: conclusive ? 'conclusive' : 'inconclusive'
196
+ };
197
+ }
198
+ function withTerminal(state) {
199
+ const terminal = terminalFor(state);
200
+ return terminal ? {
201
+ ...state,
202
+ terminal,
203
+ availabilityPhase: 'terminal'
204
+ } : state;
205
+ }
206
+ function newHotel(hotelRef) {
207
+ return {
208
+ hotelRef,
209
+ status: 'unvisited',
210
+ checksIssued: 0,
211
+ offerQueriesIssued: 0,
212
+ generationNo: 0,
213
+ generation: 0,
214
+ currentOfferRefs: [],
215
+ invalidatedOfferRefs: [],
216
+ checkCount: 0,
217
+ freshOffersRequired: false,
218
+ tombstonedOfferRefs: [],
219
+ tombstonedOfferVersionRefs: [],
220
+ lastEvidence: 'none'
221
+ };
222
+ }
223
+ function phaseForActiveHotel(state) {
224
+ const active = state.hotels[state.hotelRefs[state.activeHotelOrdinal] ?? ''];
225
+ return active?.status === 'active' && active.currentGeneration?.valid && active.currentOfferRefs.length > 0 && !active.freshOffersRequired ? 'need_check' : 'need_offers';
226
+ }
227
+ export function createAvailabilityPolicyV2(workspace) {
228
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
229
+ return {
230
+ initialized: false,
231
+ recoveryStarted: false,
232
+ availabilityPhase: 'need_offers',
233
+ activeHotelOrdinal: 0,
234
+ hotelRefs: [],
235
+ hotels: {},
236
+ attempts: [],
237
+ queryReservations: []
238
+ };
239
+ }
240
+ export function recordVisibleHotelsV2(state, _workspace) {
241
+ return copyState(state);
242
+ }
243
+ export function recordObservedOffersQueryV2(state, hotelRefs, criteriaDigest, workspace, receipt, sourceActionId, receiptDigest, criteria) {
244
+ if (workspace) assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
245
+ if (state.recoveryStarted) return copyState(state);
246
+ const next = copyState(state);
247
+ if (!workspace || !receipt) return next;
248
+ delete next.lastQueryHotelRefs;
249
+ delete next.lastQueryCriteriaDigest;
250
+ delete next.lastQueryOfferHotels;
251
+ delete next.lastQuerySourceActionId;
252
+ delete next.lastQuerySourceReceiptDigest;
253
+ delete next.lastQueryWorkspaceRevision;
254
+ const completeReceipt = receipt?.status === 'applied' && receipt.resultContract.outcome === 'complete' && receipt.resultContract.hardCriteriaMet && !receipt.resultContract.gapCodes.length && !receipt.resultContract.blockers.length && receipt.observation.kind === 'offers.state' && !receipt.observation.gapCodes?.length;
255
+ if (completeReceipt && receipt.observation.kind === 'offers.state') {
256
+ if (!criteria) throw new Error('availability_criteria_required');
257
+ delete next.criteria;
258
+ delete next.criteriaDigest;
259
+ bindCriteria(next, criteriaDigest, criteria);
260
+ if (!sourceActionId || sourceActionId !== receipt.actionId || !receiptDigest || receiptDigest !== digestV2(receipt)) throw new Error('availability_query_provenance_mismatch');
261
+ next.lastQueryHotelRefs = [
262
+ ...new Set(hotelRefs)
263
+ ];
264
+ next.lastQueryCriteriaDigest = criteriaDigest || undefined;
265
+ next.lastQueryOfferHotels = validateOffersReceiptWorkspaceV2(receipt, workspace, hotelRefs);
266
+ next.lastQuerySourceActionId = sourceActionId;
267
+ next.lastQuerySourceReceiptDigest = receiptDigest;
268
+ next.lastQueryWorkspaceRevision = workspace.revision;
269
+ } else {
270
+ delete next.criteria;
271
+ delete next.criteriaDigest;
272
+ }
273
+ return next;
274
+ }
275
+ export function validateOffersReceiptWorkspaceV2(receipt, workspace, hotelRefs) {
276
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
277
+ if (receipt.observation.kind !== 'offers.state') throw new Error('availability_offers_observation_required');
278
+ const requested = unique(hotelRefs);
279
+ if (!sameSet(receipt.observation.hotelRefs, requested)) throw new Error('availability_offers_hotel_mismatch');
280
+ const authoritative = workspace.loadedOffers.filter((offer)=>requested.includes(offer.hotelRef));
281
+ const authoritativeRefs = unique(authoritative.map((offer)=>offer.offerRef));
282
+ const observedRefs = unique(receipt.observation.offerRefs);
283
+ if (observedRefs.some((offerRef)=>!authoritativeRefs.includes(offerRef))) throw new Error('availability_offers_unreported');
284
+ const authoritativeHotels = new Set(authoritative.map((offer)=>offer.hotelRef)).size;
285
+ const observedOfferHotels = Object.fromEntries(observedRefs.map((offerRef)=>[
286
+ offerRef,
287
+ authoritative.find((offer)=>offer.offerRef === offerRef).hotelRef
288
+ ]));
289
+ const observedHotels = new Set(Object.values(observedOfferHotels)).size;
290
+ const clean = !receipt.resultContract.gapCodes.length && !receipt.resultContract.blockers.length && !receipt.observation.gapCodes?.length;
291
+ if ((receipt.status === 'no_match' || receipt.status === 'unavailable') && (receipt.resultContract.outcome !== 'empty' || observedRefs.length !== 0 || receipt.observation.loadedHotelCount !== 0)) throw new Error('availability_offers_receipt_incoherent');
292
+ const complete = receipt.status === 'applied' && receipt.resultContract.outcome === 'complete' && receipt.resultContract.hardCriteriaMet && clean;
293
+ const exactEmpty = clean && receipt.resultContract.outcome === 'empty' && ![
294
+ 'changed',
295
+ 'partial',
296
+ 'stale',
297
+ 'failed',
298
+ 'unsupported'
299
+ ].includes(receipt.status);
300
+ if ((complete || exactEmpty) && (!sameSet(observedRefs, authoritativeRefs) || receipt.observation.loadedHotelCount !== authoritativeHotels)) throw new Error('availability_offers_workspace_mismatch');
301
+ if (receipt.observation.loadedHotelCount !== observedHotels) throw new Error('availability_offers_count_mismatch');
302
+ return observedOfferHotels;
303
+ }
304
+ function sameSet(a, b) {
305
+ return a.length === b.length && unique(a).sort().every((value, index)=>value === unique(b).sort()[index]);
306
+ }
307
+ export function recordOffersQueryIssuedV2(state, hotelRefs, workspace, actionId, criteriaDigest = '', criteria) {
308
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
309
+ const next = copyState(state);
310
+ const requested = unique(hotelRefs);
311
+ if (!requested.length || requested.length > MAX_HOTELS_PER_TASK_V2) throw new Error('availability_hotel_limit_exceeded');
312
+ if (!state.recoveryStarted) throw new Error('availability_recovery_not_started');
313
+ if (state.terminal) {
314
+ if (requested.some((ref)=>state.hotels[ref]?.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL_V2)) throw new Error('availability_offer_query_limit_reached');
315
+ throw new Error('availability_terminal');
316
+ }
317
+ if (requested.some((ref)=>!next.hotels[ref])) throw new Error('availability_hotel_unknown');
318
+ 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');
319
+ bindCriteria(next, criteriaDigest, criteria);
320
+ const existingReservation = next.queryReservations.find((reservation)=>reservation.actionId === actionId);
321
+ if (existingReservation) {
322
+ if (!sameSet(existingReservation.hotelRefs, requested) || existingReservation.workspaceRevision !== workspace.revision) throw new Error('availability_query_action_conflict');
323
+ return next;
324
+ }
325
+ for (const hotelRef of requested){
326
+ const hotel = next.hotels[hotelRef];
327
+ if (hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL_V2) throw new Error('availability_offer_query_limit_reached');
328
+ hotel.offerQueriesIssued += 1;
329
+ }
330
+ next.queryReservations.push({
331
+ actionId,
332
+ hotelRefs: [
333
+ ...requested
334
+ ],
335
+ workspaceRevision: workspace.revision
336
+ });
337
+ next.availabilityPhase = 'waiting_offers';
338
+ next.initialized = true;
339
+ return next;
340
+ }
341
+ export function recordOffersGenerationV2(state, hotelRefs, workspace, actionId, receiptDigest, receipt, criteriaDigest = '', criteria) {
342
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
343
+ if (!state.recoveryStarted) return copyState(state);
344
+ if (state.terminal) throw new Error('availability_terminal');
345
+ const next = copyState(state);
346
+ const requested = unique(hotelRefs);
347
+ if (!requested.length || requested.length > MAX_HOTELS_PER_TASK_V2) throw new Error('availability_hotel_limit_exceeded');
348
+ const reservation = next.queryReservations.find((query)=>query.actionId === actionId);
349
+ if (!reservation || !sameSet(reservation.hotelRefs, requested)) throw new Error('availability_query_not_reserved');
350
+ if (receipt.actionId !== actionId) throw new Error('availability_query_receipt_mismatch');
351
+ if (receiptDigest !== digestV2(receipt)) throw new Error('availability_receipt_digest_mismatch');
352
+ if (requested.some((ref)=>!next.hotels[ref])) throw new Error('availability_hotel_unknown');
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
+ bindCriteria(next, criteriaDigest, criteria);
355
+ if (receipt.observation.kind === 'offers.state') validateOffersReceiptWorkspaceV2(receipt, workspace, requested);
356
+ for (const hotelRef of requested){
357
+ const hotel = next.hotels[hotelRef];
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);
359
+ const candidateRefs = partial ? receipt.observation.kind === 'offers.state' ? receipt.observation.offerRefs.filter((ref)=>workspace.loadedOffers.some((offer)=>offer.offerRef === ref && offer.hotelRef === hotelRef)) : [] : offerRefsForHotel(workspace, hotelRef);
360
+ const refs = legalOfferRefsForHotel(workspace, hotel, candidateRefs);
361
+ const unusable = [
362
+ 'stale',
363
+ 'failed',
364
+ 'unsupported'
365
+ ].includes(receipt.status);
366
+ if (unusable) {
367
+ hotel.currentOfferRefs = [];
368
+ hotel.invalidatedOfferRefs = [
369
+ ...hotel.currentGeneration?.orderedOfferRefs ?? []
370
+ ];
371
+ hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL_V2 && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL_V2;
372
+ hotel.status = 'inconclusive';
373
+ hotel.lastEvidence = 'inconclusive';
374
+ continue;
375
+ }
376
+ const filteredAllCandidates = candidateRefs.length > 0 && refs.length === 0;
377
+ const noLegalRefsNeedsRefresh = filteredAllCandidates && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL_V2;
378
+ hotel.generationNo += 1;
379
+ hotel.generation = hotel.generationNo;
380
+ hotel.currentOfferRefs = partial || noLegalRefsNeedsRefresh ? [] : refs;
381
+ hotel.invalidatedOfferRefs = partial || noLegalRefsNeedsRefresh ? candidateRefs : [];
382
+ hotel.freshOffersRequired = (partial || noLegalRefsNeedsRefresh) && hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL_V2 && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL_V2;
383
+ hotel.status = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'active' : 'negative';
384
+ hotel.lastEvidence = partial || noLegalRefsNeedsRefresh ? 'inconclusive' : refs.length ? 'none' : 'unavailable';
385
+ hotel.currentGeneration = {
386
+ generationId: `${hotelRef}:generation:${hotel.generationNo}`,
387
+ source: {
388
+ kind: 'query_receipt',
389
+ actionId,
390
+ receiptDigest,
391
+ workspaceRevision: workspace.revision
392
+ },
393
+ offerSetDigest: digestV2(refs),
394
+ orderedOfferRefs: refs,
395
+ evidence: partial ? 'partial' : 'complete',
396
+ valid: refs.length > 0 && !partial
397
+ };
398
+ }
399
+ const firstRequested = next.hotelRefs.indexOf(requested[0]);
400
+ if (firstRequested >= 0) next.activeHotelOrdinal = firstRequested;
401
+ const queried = next.hotels[requested[0]];
402
+ if (!queried.currentOfferRefs.length && !queried.freshOffersRequired) {
403
+ const fallback = next.hotelRefs.findIndex((ref)=>[
404
+ 'active',
405
+ 'unvisited'
406
+ ].includes(next.hotels[ref].status));
407
+ if (fallback >= 0) next.activeHotelOrdinal = fallback;
408
+ }
409
+ next.queryReservations = next.queryReservations.filter((query)=>query.actionId !== actionId);
410
+ next.initialized = true;
411
+ next.availabilityPhase = phaseForActiveHotel(next);
412
+ delete next.terminal;
413
+ return withTerminal(next);
414
+ }
415
+ export function canIssueOfferCheckV2(state, workspace, offerRef, offerVersionRef) {
416
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
417
+ if (!state.recoveryStarted) {
418
+ const loaded = loadedOfferVersion(workspace, offerRef, offerVersionRef);
419
+ return loaded ? {
420
+ ok: true,
421
+ hotelRef: loaded.hotelRef,
422
+ offerRef,
423
+ offerVersionRef,
424
+ checkCount: 0,
425
+ generation: 0
426
+ } : {
427
+ ok: false,
428
+ code: 'offer_not_loaded'
429
+ };
430
+ }
431
+ if (state.terminal) return {
432
+ ok: false,
433
+ code: 'availability_exhausted'
434
+ };
435
+ const loaded = loadedOfferVersion(workspace, offerRef, offerVersionRef);
436
+ if (!loaded) return {
437
+ ok: false,
438
+ code: 'offer_not_loaded'
439
+ };
440
+ const hotel = state.hotels[loaded.hotelRef];
441
+ if (!hotel) return {
442
+ ok: false,
443
+ code: 'hotel_unknown'
444
+ };
445
+ if (state.hotelRefs[state.activeHotelOrdinal] !== loaded.hotelRef) return {
446
+ ok: false,
447
+ code: 'hotel_not_active'
448
+ };
449
+ if (hotel.freshOffersRequired) return {
450
+ ok: false,
451
+ code: 'offers_refresh_required'
452
+ };
453
+ if (hotel.tombstonedOfferRefs.includes(offerRef) || hotel.tombstonedOfferVersionRefs.includes(offerVersionRef)) return {
454
+ ok: false,
455
+ code: 'generation_invalid'
456
+ };
457
+ if (!hotel.currentGeneration?.valid || !hotel.currentGeneration.orderedOfferRefs.includes(offerRef) || hotel.invalidatedOfferRefs.includes(offerRef)) return {
458
+ ok: false,
459
+ code: 'generation_invalid'
460
+ };
461
+ if (hotel.checksIssued >= MAX_OFFER_CHECKS_PER_HOTEL_V2) return {
462
+ ok: false,
463
+ code: 'hotel_check_limit_reached'
464
+ };
465
+ return {
466
+ ok: true,
467
+ hotelRef: loaded.hotelRef,
468
+ offerRef,
469
+ offerVersionRef,
470
+ checkCount: hotel.checksIssued + 1,
471
+ generation: hotel.generationNo
472
+ };
473
+ }
474
+ function startRecoveryAtOfferV2(state, workspace, offerRef, actionId) {
475
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
476
+ const loaded = workspace.loadedOffers.find((offer)=>offer.offerRef === offerRef);
477
+ if (!loaded) return copyState(state);
478
+ const shortlistHotels = workspace.shortlistedOfferRefs.map((ref)=>workspace.loadedOffers.find((offer)=>offer.offerRef === ref)?.hotelRef ?? state.lastQueryOfferHotels?.[ref]).map((ref, index)=>{
479
+ if (!ref) throw new Error(`availability_shortlist_unmapped:${workspace.shortlistedOfferRefs[index]}`);
480
+ return ref;
481
+ });
482
+ const fallbackHotels = shortlistHotels.length ? shortlistHotels : [];
483
+ const candidates = unique([
484
+ loaded.hotelRef,
485
+ ...fallbackHotels
486
+ ]).slice(0, MAX_HOTELS_PER_TASK_V2);
487
+ const next = copyState(state);
488
+ next.recoveryStarted = true;
489
+ next.recoveryId = `recovery:${actionId}`;
490
+ next.hotelRefs = candidates;
491
+ next.candidateSetDigest = digestV2(candidates);
492
+ next.hotels = Object.fromEntries(candidates.map((ref)=>[
493
+ ref,
494
+ newHotel(ref)
495
+ ]));
496
+ const nextHotel = next.hotels[loaded.hotelRef];
497
+ const mappedRefs = state.lastQueryOfferHotels ? Object.entries(state.lastQueryOfferHotels).filter(([, hotel])=>hotel === loaded.hotelRef).map(([ref])=>ref) : [];
498
+ const currentRefs = unique(workspace.loadedOffers.filter((offer)=>offer.hotelRef === loaded.hotelRef).map((offer)=>offer.offerRef));
499
+ const mapped = state.lastQueryOfferHotels?.[offerRef] === loaded.hotelRef && state.lastQueryWorkspaceRevision === workspace.revision && Boolean(state.lastQuerySourceActionId && state.lastQuerySourceReceiptDigest) && sameSet(mappedRefs, currentRefs);
500
+ if (mapped && state.criteria) {
501
+ next.criteria = structuredClone(state.criteria);
502
+ next.criteriaDigest = digestV2(next.criteria);
503
+ }
504
+ const refs = mapped && state.lastQueryOfferHotels ? unique([
505
+ offerRef,
506
+ ...mappedRefs
507
+ ]).slice(0, MAX_OFFERS_PER_HOTEL_GENERATION_V2) : offerRefsForHotel(workspace, loaded.hotelRef, offerRef);
508
+ const source = mapped ? {
509
+ kind: 'query_receipt',
510
+ actionId: state.lastQuerySourceActionId,
511
+ receiptDigest: state.lastQuerySourceReceiptDigest,
512
+ workspaceRevision: state.lastQueryWorkspaceRevision
513
+ } : {
514
+ kind: 'workspace_snapshot',
515
+ workspaceDigest: workspaceDigestV2(workspace),
516
+ workspaceRevision: workspace.revision
517
+ };
518
+ nextHotel.generationNo = 1;
519
+ nextHotel.generation = 1;
520
+ nextHotel.currentOfferRefs = refs;
521
+ nextHotel.status = refs.length ? 'active' : 'inconclusive';
522
+ nextHotel.currentGeneration = {
523
+ generationId: `${loaded.hotelRef}:generation:1`,
524
+ source,
525
+ offerSetDigest: digestV2(refs),
526
+ orderedOfferRefs: refs,
527
+ evidence: 'complete',
528
+ valid: refs.length > 0
529
+ };
530
+ next.initialized = true;
531
+ next.availabilityPhase = 'need_check';
532
+ return next;
533
+ }
534
+ export function recordOfferCheckIssuedV2(state, workspace, offerRef, offerVersionRef, actionId = `offer-check-${offerRef}-${state.attempts.length + 1}`) {
535
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
536
+ if (state.terminal) throw new Error('availability_terminal');
537
+ if (!state.recoveryStarted) {
538
+ if (!loadedOfferVersion(workspace, offerRef, offerVersionRef)) throw new Error('availability_offer_not_loaded');
539
+ const started = startRecoveryAtOfferV2(state, workspace, offerRef, actionId);
540
+ return recordOfferCheckIssuedV2(started, workspace, offerRef, offerVersionRef, actionId);
541
+ }
542
+ const decision = canIssueOfferCheckV2(state, workspace, offerRef, offerVersionRef);
543
+ if (!decision.ok) throw new Error(`availability_${decision.code}`);
544
+ const next = copyState(state);
545
+ const hotel = next.hotels[decision.hotelRef];
546
+ hotel.checksIssued += 1;
547
+ hotel.checkCount = hotel.checksIssued;
548
+ next.attempts.push({
549
+ actionId,
550
+ hotelRef: decision.hotelRef,
551
+ offerRef,
552
+ offerVersionRef,
553
+ generation: hotel.generationNo,
554
+ ordinal: hotel.checksIssued,
555
+ workspaceRevision: workspace.revision
556
+ });
557
+ hotel.currentGeneration.valid = false;
558
+ next.availabilityPhase = 'waiting_check';
559
+ return next;
560
+ }
561
+ export function reduceAvailabilityActionV2(state, workspace, action) {
562
+ if (action.kind === 'offers.query') {
563
+ if (!isOfferCriteriaV2(action.input.criteria) && (!state.criteria || !isOfferCriteriaV2(state.criteria))) throw new Error('availability_criteria_required');
564
+ if (!isOfferCriteriaV2(action.input.criteria) && state.recoveryStarted) throw new Error('availability_criteria_required');
565
+ if (!isOfferCriteriaV2(action.input.criteria)) throw new Error('availability_criteria_required');
566
+ const criteriaDigest = digestV2(action.input.criteria);
567
+ let next = recordObservedOffersQueryV2(state, action.input.hotelRefs, criteriaDigest);
568
+ if (state.recoveryStarted) next = recordOffersQueryIssuedV2(next, action.input.hotelRefs, workspace, action.actionId, criteriaDigest, action.input.criteria);
569
+ return next;
570
+ }
571
+ if (action.kind === 'offer.check') return recordOfferCheckIssuedV2(state, workspace, action.input.offerRef, action.input.offerVersionRef, action.actionId);
572
+ return copyState(state);
573
+ }
574
+ export function recordOfferCheckReceiptV2(state, workspace, receipt, actionId, offerRef, offerVersionRef, expectedRevision) {
575
+ assertWorkspaceLoadedOfferRefsUniqueV2(workspace);
576
+ if (!state.recoveryStarted) return copyState(state);
577
+ const attempt = state.attempts.find((candidate)=>candidate.actionId === actionId);
578
+ if (!attempt) throw new Error('availability_attempt_unknown');
579
+ if (receipt.actionId !== actionId) throw new Error('availability_attempt_mismatch');
580
+ if (receipt.revision !== workspace.revision || receipt.revision < expectedRevision) throw new Error('availability_stale_receipt');
581
+ const targetOfferRef = receipt.observation.kind === 'offer.availability' ? receipt.observation.offerRef : offerRef;
582
+ const targetVersionRef = receipt.observation.kind === 'offer.availability' ? receipt.observation.checkedOfferVersionRef : offerVersionRef;
583
+ if (attempt.offerRef !== targetOfferRef || attempt.offerVersionRef !== targetVersionRef || offerRef !== targetOfferRef || offerVersionRef !== targetVersionRef || attempt.workspaceRevision !== expectedRevision) throw new Error('availability_attempt_mismatch');
584
+ const next = copyState(state);
585
+ const hotel = next.hotels[attempt.hotelRef];
586
+ if (!hotel || !hotel.currentGeneration || attempt.generation !== hotel.generationNo) throw new Error('availability_generation_mismatch');
587
+ const observation = receipt.observation.kind === 'offer.availability' ? receipt.observation : undefined;
588
+ if ([
589
+ 'applied',
590
+ 'changed',
591
+ 'unavailable',
592
+ 'no_match'
593
+ ].includes(receipt.status) && !observation) throw new Error('availability_observation_required');
594
+ if (observation?.available && (receipt.resultContract.outcome === 'empty' || [
595
+ 'unavailable',
596
+ 'no_match',
597
+ 'failed',
598
+ 'stale',
599
+ 'unsupported'
600
+ ].includes(receipt.status))) throw new Error('availability_receipt_incoherent');
601
+ if (observation && !observation.available && (observation.verifiedOfferRef || observation.currentOfferVersionRef)) throw new Error('availability_receipt_incoherent');
602
+ if ([
603
+ 'unavailable',
604
+ 'no_match'
605
+ ].includes(receipt.status) && receipt.resultContract.outcome !== 'empty') throw new Error('availability_receipt_incoherent');
606
+ const confirmed = receipt.status === 'applied' && Boolean(observation?.available && observation.currentOfferVersionRef === offerVersionRef && observation.verifiedOfferRef && workspace.verifiedOffer?.offerRef === offerRef && workspace.verifiedOffer.offerVersionRef === offerVersionRef && workspace.verifiedOffer.verifiedOfferRef === observation.verifiedOfferRef && loadedOfferVersion(workspace, offerRef, offerVersionRef)) && !observation?.changedFactRefs.length && !observation?.gapCodes?.length && receipt.resultContract.outcome === 'complete' && receipt.resultContract.hardCriteriaMet && !receipt.resultContract.blockers.length && !receipt.resultContract.gapCodes.length;
607
+ if (confirmed) {
608
+ hotel.status = 'confirmed';
609
+ hotel.lastEvidence = 'confirmed';
610
+ hotel.currentGeneration.valid = false;
611
+ return withTerminal(next);
612
+ }
613
+ if (receipt.status === 'changed' && observation?.available && observation.currentOfferVersionRef && observation.currentOfferVersionRef !== offerVersionRef && observation.changedFactRefs.length && !observation.verifiedOfferRef && loadedOfferVersion(workspace, offerRef, observation.currentOfferVersionRef)) {
614
+ hotel.tombstonedOfferVersionRefs = unique([
615
+ ...hotel.tombstonedOfferVersionRefs,
616
+ offerVersionRef
617
+ ]);
618
+ hotel.currentGeneration.valid = true;
619
+ hotel.invalidatedOfferRefs = [];
620
+ hotel.currentOfferRefs = [
621
+ offerRef
622
+ ];
623
+ hotel.freshOffersRequired = false;
624
+ hotel.status = 'active';
625
+ hotel.lastEvidence = 'none';
626
+ next.availabilityPhase = 'need_check';
627
+ return withTerminal(next);
628
+ }
629
+ const completeNegative = (receipt.status === 'unavailable' || receipt.status === 'no_match') && receipt.resultContract.outcome === 'empty' && observation?.available === false && receipt.resultContract.gapCodes.length === 0 && !receipt.resultContract.blockers.length && !observation.gapCodes?.length;
630
+ if (completeNegative) {
631
+ hotel.tombstonedOfferVersionRefs = unique([
632
+ ...hotel.tombstonedOfferVersionRefs,
633
+ offerVersionRef
634
+ ]);
635
+ hotel.tombstonedOfferRefs = unique([
636
+ ...hotel.tombstonedOfferRefs,
637
+ offerRef
638
+ ]);
639
+ }
640
+ hotel.currentGeneration.valid = false;
641
+ hotel.invalidatedOfferRefs = [
642
+ ...hotel.currentGeneration.orderedOfferRefs
643
+ ];
644
+ hotel.currentOfferRefs = [];
645
+ hotel.freshOffersRequired = hotel.checksIssued < MAX_OFFER_CHECKS_PER_HOTEL_V2 && hotel.offerQueriesIssued < MAX_OFFER_QUERIES_PER_HOTEL_V2;
646
+ hotel.status = completeNegative ? 'negative' : 'inconclusive';
647
+ hotel.lastEvidence = completeNegative ? 'unavailable' : 'inconclusive';
648
+ const currentOrdinal = next.hotelRefs.indexOf(attempt.hotelRef);
649
+ const nextOrdinal = hotel.freshOffersRequired ? currentOrdinal : next.hotelRefs.findIndex((ref)=>[
650
+ 'active',
651
+ 'unvisited'
652
+ ].includes(next.hotels[ref].status));
653
+ if (nextOrdinal >= 0) next.activeHotelOrdinal = nextOrdinal;
654
+ next.availabilityPhase = phaseForActiveHotel(next);
655
+ return withTerminal(next);
656
+ }
657
+ export function reduceAvailabilityReceiptV2(state, workspace, receipt, action) {
658
+ let next = recordVisibleHotelsV2(state, workspace);
659
+ if (action.kind === 'offers.query' && !state.recoveryStarted) {
660
+ if (!isOfferCriteriaV2(action.input.criteria)) throw new Error('availability_criteria_required');
661
+ next = recordObservedOffersQueryV2(next, action.input.hotelRefs, digestV2(action.input.criteria), workspace, receipt, action.actionId, digestV2(receipt), action.input.criteria);
662
+ } else if (action.kind === 'offers.query' && state.recoveryStarted) {
663
+ if (!isOfferCriteriaV2(action.input.criteria)) throw new Error('availability_criteria_required');
664
+ next = recordOffersGenerationV2(next, action.input.hotelRefs, workspace, action.actionId, digestV2(receipt), receipt, state.criteriaDigest ?? '', action.input.criteria);
665
+ } else if (action.kind === 'offer.check') {
666
+ if (action.expectedRevision === undefined) throw new Error('availability_expected_revision_missing');
667
+ next = recordOfferCheckReceiptV2(next, workspace, receipt, action.actionId, action.input.offerRef, action.input.offerVersionRef, action.expectedRevision);
668
+ }
669
+ return next;
670
+ }
671
+ export function availabilityPolicyIsTerminalV2(state) {
672
+ return Boolean(state.terminal);
673
+ }
674
+ export function availabilityPolicyResultV2(state) {
675
+ return state.terminal ? {
676
+ ...state.terminal,
677
+ hotelRefs: [
678
+ ...state.terminal.hotelRefs
679
+ ]
680
+ } : undefined;
681
+ }
682
+ export function validateAvailabilityPolicyV2(state) {
683
+ try {
684
+ if (!state || typeof state.initialized !== 'boolean' || typeof state.recoveryStarted !== 'boolean') return false;
685
+ if (state.recoveryId !== undefined && (typeof state.recoveryId !== 'string' || !state.recoveryId)) return false;
686
+ if (state.criteriaDigest !== undefined && (typeof state.criteriaDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.criteriaDigest))) return false;
687
+ if (state.candidateSetDigest !== undefined && (typeof state.candidateSetDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.candidateSetDigest))) return false;
688
+ if (![
689
+ 'need_offers',
690
+ 'waiting_offers',
691
+ 'need_check',
692
+ 'waiting_check',
693
+ 'terminal'
694
+ ].includes(state.availabilityPhase)) return false;
695
+ if (!Number.isSafeInteger(state.activeHotelOrdinal) || !Array.isArray(state.hotelRefs) || state.hotelRefs.length > MAX_HOTELS_PER_TASK_V2) return false;
696
+ if (state.hotelRefs.some((ref)=>typeof ref !== 'string' || !ref) || new Set(state.hotelRefs).size !== state.hotelRefs.length) return false;
697
+ if (state.hotelRefs.length && (state.activeHotelOrdinal < 0 || state.activeHotelOrdinal >= state.hotelRefs.length) || !state.hotelRefs.length && state.activeHotelOrdinal !== 0) return false;
698
+ if (!state.hotels || typeof state.hotels !== 'object' || Object.keys(state.hotels).sort().join('\0') !== [
699
+ ...state.hotelRefs
700
+ ].sort().join('\0')) return false;
701
+ if (!Array.isArray(state.attempts) || !Array.isArray(state.queryReservations)) return false;
702
+ if (!state.recoveryStarted) {
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
+ if (state.criteria !== undefined || state.criteriaDigest !== undefined) {
705
+ if (!isOfferCriteriaV2(state.criteria) || !state.criteriaDigest || state.criteriaDigest !== digestV2(state.criteria)) return false;
706
+ }
707
+ if (state.lastQueryCriteriaDigest !== undefined && state.lastQueryCriteriaDigest !== state.criteriaDigest) return false;
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;
709
+ if (state.lastQueryOfferHotels !== undefined && (!state.lastQueryHotelRefs || Object.values(state.lastQueryOfferHotels).some((ref)=>typeof ref !== 'string' || !state.lastQueryHotelRefs.includes(ref)) || Object.keys(state.lastQueryOfferHotels).some((ref)=>typeof ref !== 'string' || !ref))) return false;
710
+ if (state.lastQuerySourceActionId !== undefined && (typeof state.lastQuerySourceActionId !== 'string' || !state.lastQuerySourceActionId || typeof state.lastQuerySourceReceiptDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.lastQuerySourceReceiptDigest) || !Number.isSafeInteger(state.lastQueryWorkspaceRevision) || state.lastQueryWorkspaceRevision < 0)) return false;
711
+ if (state.lastQuerySourceReceiptDigest !== undefined && state.lastQuerySourceActionId === undefined) return false;
712
+ return state.initialized === false && state.availabilityPhase === 'need_offers';
713
+ }
714
+ if (!state.initialized || !state.recoveryId || !state.candidateSetDigest || state.candidateSetDigest !== digestV2(state.hotelRefs)) return false;
715
+ const criteriaBound = isOfferCriteriaV2(state.criteria) && Boolean(state.criteriaDigest) && state.criteriaDigest === digestV2(state.criteria);
716
+ const manualCheckWorkspaceSnapshot = state.attempts.length > 0 && state.attempts.every((attempt)=>state.hotels[attempt.hotelRef]?.currentGeneration?.source.kind === 'workspace_snapshot');
717
+ const manualCheckMayBindCriteria = !state.criteria && !state.criteriaDigest && state.queryReservations.length === 0 && manualCheckWorkspaceSnapshot && ([
718
+ 'waiting_check',
719
+ 'need_offers'
720
+ ].includes(state.availabilityPhase) || state.availabilityPhase === 'need_check' || state.availabilityPhase === 'terminal' && state.terminal?.code === 'availability_confirmed');
721
+ if (!criteriaBound && !manualCheckMayBindCriteria) return false;
722
+ if (state.lastQueryCriteriaDigest !== undefined && state.lastQueryCriteriaDigest !== state.criteriaDigest) return false;
723
+ 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;
724
+ if (state.lastQueryOfferHotels !== undefined && (!state.lastQueryHotelRefs || Object.values(state.lastQueryOfferHotels).some((ref)=>typeof ref !== 'string' || !state.lastQueryHotelRefs.includes(ref)) || Object.keys(state.lastQueryOfferHotels).some((ref)=>typeof ref !== 'string' || !ref))) return false;
725
+ if (state.lastQuerySourceActionId !== undefined && (typeof state.lastQuerySourceActionId !== 'string' || !state.lastQuerySourceActionId || typeof state.lastQuerySourceReceiptDigest !== 'string' || !/^[0-9a-f]{64}$/.test(state.lastQuerySourceReceiptDigest) || !Number.isSafeInteger(state.lastQueryWorkspaceRevision) || state.lastQueryWorkspaceRevision < 0)) return false;
726
+ if (state.lastQuerySourceReceiptDigest !== undefined && state.lastQuerySourceActionId === undefined) return false;
727
+ if (state.hotelRefs.length === 0) return false;
728
+ const validStatus = new Set([
729
+ 'unvisited',
730
+ 'active',
731
+ 'negative',
732
+ 'confirmed',
733
+ 'inconclusive'
734
+ ]);
735
+ const validEvidence = new Set([
736
+ 'none',
737
+ 'confirmed',
738
+ 'unavailable',
739
+ 'inconclusive'
740
+ ]);
741
+ for (const ref of state.hotelRefs){
742
+ const hotel = state.hotels[ref];
743
+ if (!hotel || hotel.hotelRef !== ref || !validStatus.has(hotel.status) || !validEvidence.has(hotel.lastEvidence)) return false;
744
+ if (![
745
+ hotel.checksIssued,
746
+ hotel.offerQueriesIssued,
747
+ hotel.generationNo,
748
+ hotel.generation,
749
+ hotel.checkCount
750
+ ].every(Number.isSafeInteger)) return false;
751
+ if (hotel.checksIssued < 0 || hotel.checksIssued > MAX_OFFER_CHECKS_PER_HOTEL_V2 || hotel.offerQueriesIssued < 0 || hotel.offerQueriesIssued > MAX_OFFER_QUERIES_PER_HOTEL_V2 || hotel.generationNo < 0 || hotel.generation !== hotel.generationNo || hotel.checkCount !== hotel.checksIssued) return false;
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
+ 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 > MAX_OFFERS_PER_HOTEL_GENERATION_V2 || hotel.invalidatedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION_V2 || hotel.currentOfferRefs.some((offer)=>hotel.invalidatedOfferRefs.includes(offer) || hotel.tombstonedOfferRefs.includes(offer))) return false;
755
+ const generation = hotel.currentGeneration;
756
+ if (hotel.generationNo === 0) {
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
+ continue;
759
+ }
760
+ if (!generation || generation.generationId !== `${ref}:generation:${hotel.generationNo}` || !/^[0-9a-f]{64}$/.test(generation.offerSetDigest) || generation.offerSetDigest !== digestV2(generation.orderedOfferRefs) || !Array.isArray(generation.orderedOfferRefs) || generation.orderedOfferRefs.length > MAX_OFFERS_PER_HOTEL_GENERATION_V2 || generation.orderedOfferRefs.some((offer)=>typeof offer !== 'string' || !offer) || new Set(generation.orderedOfferRefs).size !== generation.orderedOfferRefs.length || ![
761
+ 'complete',
762
+ 'partial'
763
+ ].includes(generation.evidence) || typeof generation.valid !== 'boolean') return false;
764
+ if (generation.offerSetDigest !== digestV2(generation.orderedOfferRefs) || hotel.currentOfferRefs.some((offer)=>!generation.orderedOfferRefs.includes(offer)) || hotel.invalidatedOfferRefs.some((offer)=>!generation.orderedOfferRefs.includes(offer))) return false;
765
+ const source = generation.source;
766
+ if (!source || ![
767
+ 'query_receipt',
768
+ 'workspace_snapshot'
769
+ ].includes(source.kind) || !Number.isSafeInteger(source.workspaceRevision) || source.workspaceRevision < 0) return false;
770
+ if (source.kind === 'query_receipt' && (typeof source.actionId !== 'string' || !source.actionId || !/^[0-9a-f]{64}$/.test(source.receiptDigest))) return false;
771
+ if (source.kind === 'workspace_snapshot' && (typeof source.workspaceDigest !== 'string' || !/^[0-9a-f]{64}$/.test(source.workspaceDigest))) return false;
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 >= MAX_OFFER_CHECKS_PER_HOTEL_V2 || hotel.offerQueriesIssued >= MAX_OFFER_QUERIES_PER_HOTEL_V2)) return false;
774
+ if (hotel.status === 'unvisited' || hotel.status === 'negative') {
775
+ if (hotel.status === 'unvisited' || hotel.lastEvidence !== 'unavailable' || hotel.currentOfferRefs.length || generation.valid) return false;
776
+ }
777
+ if (hotel.status === 'active' && (!hotel.currentOfferRefs.length || hotel.lastEvidence !== 'none' || hotel.freshOffersRequired)) return false;
778
+ if (hotel.status === 'confirmed' && (hotel.lastEvidence !== 'confirmed' || !hotel.currentOfferRefs.length || generation.valid)) return false;
779
+ if (hotel.status === 'inconclusive' && (hotel.lastEvidence !== 'inconclusive' || hotel.currentOfferRefs.length || generation.valid)) return false;
780
+ }
781
+ const attempts = state.attempts;
782
+ if (new Set(attempts.map((attempt)=>attempt.actionId)).size !== attempts.length) return false;
783
+ const attemptsByHotel = new Map();
784
+ for (const attempt of attempts){
785
+ if (typeof attempt.actionId !== 'string' || !attempt.actionId || !state.hotels[attempt.hotelRef] || typeof attempt.offerRef !== 'string' || !attempt.offerRef || typeof attempt.offerVersionRef !== 'string' || !attempt.offerVersionRef || !Number.isSafeInteger(attempt.generation) || !Number.isSafeInteger(attempt.ordinal) || !Number.isSafeInteger(attempt.workspaceRevision) || attempt.workspaceRevision < 0) return false;
786
+ const hotel = state.hotels[attempt.hotelRef];
787
+ const generation = hotel.currentGeneration;
788
+ if (!generation || attempt.generation < 1 || attempt.generation > hotel.generationNo || attempt.ordinal < 1 || attempt.ordinal > hotel.checksIssued || attempt.generation === hotel.generationNo && !generation.orderedOfferRefs.includes(attempt.offerRef)) return false;
789
+ const list = attemptsByHotel.get(attempt.hotelRef) ?? [];
790
+ list.push(attempt);
791
+ attemptsByHotel.set(attempt.hotelRef, list);
792
+ }
793
+ for (const [ref, attemptsForHotel] of attemptsByHotel){
794
+ const ordinals = attemptsForHotel.map((attempt)=>attempt.ordinal);
795
+ if (new Set(ordinals).size !== ordinals.length || attemptsForHotel.length !== state.hotels[ref].checksIssued || new Set(attemptsForHotel.map((attempt)=>`${attempt.generation}:${attempt.offerRef}:${attempt.offerVersionRef}`)).size !== attemptsForHotel.length) return false;
796
+ }
797
+ for (const ref of state.hotelRefs)if ((attemptsByHotel.get(ref)?.length ?? 0) !== state.hotels[ref].checksIssued) return false;
798
+ if (new Set(state.queryReservations.map((reservation)=>reservation.actionId)).size !== state.queryReservations.length) return false;
799
+ const reservedHotels = new Set();
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 > MAX_HOTELS_PER_TASK_V2 || 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
+ reservation.hotelRefs.forEach((ref)=>reservedHotels.add(ref));
803
+ }
804
+ if (state.availabilityPhase === 'waiting_offers' && state.queryReservations.length === 0) return false;
805
+ if (state.availabilityPhase !== 'waiting_offers' && state.queryReservations.length > 0) return false;
806
+ if (state.availabilityPhase === 'waiting_check' && !attempts.length) return false;
807
+ if (state.availabilityPhase === 'need_check') {
808
+ const active = state.hotels[state.hotelRefs[state.activeHotelOrdinal]];
809
+ if (active.status !== 'active' || !active.currentGeneration?.valid || !active.currentOfferRefs.length || active.freshOffersRequired) return false;
810
+ }
811
+ if (state.availabilityPhase === 'terminal' && !state.terminal) return false;
812
+ if (state.availabilityPhase !== 'terminal' && state.terminal) return false;
813
+ const recomputedTerminal = terminalFor(state);
814
+ if (state.terminal) {
815
+ if (!recomputedTerminal || digestV2(state.terminal) !== digestV2(recomputedTerminal)) return false;
816
+ } else if (recomputedTerminal && ![
817
+ 'waiting_offers',
818
+ 'waiting_check'
819
+ ].includes(state.availabilityPhase)) return false;
820
+ return true;
821
+ } catch {
822
+ return false;
823
+ }
824
+ }
825
+ export function availabilityPolicyActionIsRelevantV2(action) {
826
+ return action.kind === 'offers.query' || action.kind === 'offer.check';
827
+ }
828
+
829
+
830
+ //# sourceURL=ts/src/booking-surface/availability-policy-v2.ts