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

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 +47 -13
  2. package/README.zh-CN.md +20 -10
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +111 -51
  5. package/bin/gotry-inner.js +442 -60
  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 +30 -10
  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,202 @@
1
+ import assert from 'node:assert/strict';
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { ensureLedger } from '../src/state-ledger.js';
8
+ import { BookingCopilotTaskRuntime } from '../src/booking-surface/runtime.js';
9
+ const taskId = 'task-event-sequence-atomicity';
10
+ const workspace = {
11
+ schemaVersion: 'booking.surface.v1',
12
+ contextRef: 'ctx-event-sequence-atomicity',
13
+ surface: 'tenant',
14
+ revision: 0,
15
+ locale: 'en-US',
16
+ currency: 'AED',
17
+ searchDraft: {},
18
+ results: {
19
+ status: 'idle'
20
+ },
21
+ visibleHotels: [],
22
+ loadedOffers: [],
23
+ shortlistedOfferRefs: [],
24
+ capabilities: {
25
+ surface: 'tenant',
26
+ allowedActions: [
27
+ 'search.run'
28
+ ]
29
+ }
30
+ };
31
+ const turn = {
32
+ schemaVersion: 'booking.surface.v1',
33
+ kind: 'user.turn',
34
+ taskId,
35
+ workspace,
36
+ request: {
37
+ text: 'opaque user turn'
38
+ }
39
+ };
40
+ const childIndex = process.argv.indexOf('--child');
41
+ const child = childIndex >= 0;
42
+ const childRoot = child ? process.argv[childIndex + 1] : undefined;
43
+ const childMode = child ? process.argv[childIndex + 2] : undefined;
44
+ const childVariant = child ? process.argv[childIndex + 3] : undefined;
45
+ function sleepWhile(predicate, timeoutMs = 10_000) {
46
+ const deadline = Date.now() + timeoutMs;
47
+ while(!predicate()){
48
+ if (Date.now() > deadline) throw new Error('proof_barrier_timeout');
49
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
50
+ }
51
+ }
52
+ function waitForFile(path, timeoutMs = 10_000) {
53
+ return new Promise((resolve, reject)=>{
54
+ const deadline = Date.now() + timeoutMs;
55
+ const poll = ()=>{
56
+ if (existsSync(path)) return resolve();
57
+ if (Date.now() > deadline) return reject(new Error(`proof_file_timeout:${path}`));
58
+ setTimeout(poll, 5);
59
+ };
60
+ poll();
61
+ });
62
+ }
63
+ function runChild(root, mode, variant) {
64
+ const script = fileURLToPath(import.meta.url);
65
+ const tsx = fileURLToPath(new URL('../node_modules/tsx/dist/cli.mjs', import.meta.url));
66
+ const processChild = spawn(process.execPath, [
67
+ tsx,
68
+ script,
69
+ '--child',
70
+ root,
71
+ mode,
72
+ variant
73
+ ], {
74
+ cwd: process.cwd(),
75
+ stdio: [
76
+ 'ignore',
77
+ 'pipe',
78
+ 'pipe'
79
+ ]
80
+ });
81
+ let output = '';
82
+ processChild.stdout.on('data', (chunk)=>{
83
+ output += chunk.toString();
84
+ });
85
+ processChild.stderr.on('data', (chunk)=>{
86
+ output += chunk.toString();
87
+ });
88
+ return new Promise((resolve, reject)=>{
89
+ const timer = setTimeout(()=>{
90
+ processChild.kill('SIGKILL');
91
+ reject(new Error(`child_timeout:${mode}:${variant}:${output}`));
92
+ }, 15_000);
93
+ processChild.on('exit', (code)=>{
94
+ clearTimeout(timer);
95
+ if (code !== 0) return reject(new Error(`child_exit_${code}:${mode}:${variant}:${output}`));
96
+ try {
97
+ resolve(JSON.parse(output.trim().split('\n').at(-1)));
98
+ } catch (error) {
99
+ reject(new Error(`child_output_invalid:${mode}:${variant}:${output}:${error}`));
100
+ }
101
+ });
102
+ });
103
+ }
104
+ if (child) {
105
+ const root = childRoot;
106
+ const mode = childMode;
107
+ const variant = childVariant;
108
+ const ledger = ensureLedger(root);
109
+ const runtime = new BookingCopilotTaskRuntime(ledger);
110
+ writeFileSync(join(root, `started-${mode}-${variant}`), 'started');
111
+ sleepWhile(()=>existsSync(join(root, 'start')));
112
+ const originalResume = runtime.resumeTask.bind(runtime);
113
+ let firstRead = true;
114
+ runtime.resumeTask = (id)=>{
115
+ const state = originalResume(id);
116
+ if (firstRead && state) {
117
+ firstRead = false;
118
+ writeFileSync(join(root, `ready-${mode}-${variant}`), 'ready');
119
+ sleepWhile(()=>existsSync(join(root, 'release')));
120
+ }
121
+ return state;
122
+ };
123
+ try {
124
+ if (mode === 'user-turn') runtime.startTask(turn);
125
+ const event = runtime.emitEvent(taskId, {
126
+ kind: 'status',
127
+ status: 'working'
128
+ });
129
+ console.log(JSON.stringify({
130
+ event: {
131
+ sequence: event.sequence
132
+ }
133
+ }));
134
+ } catch (error) {
135
+ console.log(JSON.stringify({
136
+ error: error instanceof Error ? error.message : String(error)
137
+ }));
138
+ process.exitCode = 1;
139
+ } finally{
140
+ ledger.close();
141
+ }
142
+ } else {
143
+ async function run(mode) {
144
+ const root = mkdtempSync(join(tmpdir(), `gotry-event-sequence-${mode}-`));
145
+ mkdirSync(root, {
146
+ recursive: true
147
+ });
148
+ const ledger = ensureLedger(root);
149
+ new BookingCopilotTaskRuntime(ledger).startTask(turn);
150
+ ledger.close();
151
+ const children = [
152
+ runChild(root, mode, 'left'),
153
+ runChild(root, mode, 'right')
154
+ ];
155
+ try {
156
+ await Promise.all([
157
+ waitForFile(join(root, `started-${mode}-left`)),
158
+ waitForFile(join(root, `started-${mode}-right`))
159
+ ]);
160
+ writeFileSync(join(root, 'start'), 'start');
161
+ await Promise.race([
162
+ waitForFile(join(root, `ready-${mode}-left`)),
163
+ waitForFile(join(root, `ready-${mode}-right`))
164
+ ]);
165
+ await new Promise((resolve)=>setTimeout(resolve, 250));
166
+ assert.equal(existsSync(join(root, `ready-${mode}-left`)) && existsSync(join(root, `ready-${mode}-right`)), false, `${mode} workers both derived state before the first transaction committed`);
167
+ writeFileSync(join(root, 'release'), 'release');
168
+ const results = await Promise.all(children);
169
+ assert.deepEqual(results.map((result)=>result.error), [
170
+ undefined,
171
+ undefined
172
+ ]);
173
+ const check = ensureLedger(root);
174
+ const rows = check.db.prepare("SELECT payload FROM events WHERE tenant_id = ? AND run_id = ? AND kind = 'booking.copilot.event.emitted' ORDER BY seq").all(check.tenant, taskId);
175
+ const sequences = rows.map((row)=>JSON.parse(row.payload).sequence);
176
+ assert.deepEqual(sequences, [
177
+ 1,
178
+ 2
179
+ ], `${mode} durable event sequences must be unique and strictly monotonic`);
180
+ assert.deepEqual(results.map((result)=>result.event?.sequence).sort((a, b)=>a - b), [
181
+ 1,
182
+ 2
183
+ ]);
184
+ if (mode === 'user-turn') {
185
+ const userTurns = check.db.prepare("SELECT COUNT(*) AS n FROM events WHERE tenant_id = ? AND run_id = ? AND kind = 'booking.copilot.user.turn.observed'").get(check.tenant, taskId);
186
+ assert.equal(userTurns.n, 3, 'initial plus two concurrent user turns must all be durable');
187
+ }
188
+ check.close();
189
+ } finally{
190
+ rmSync(root, {
191
+ recursive: true,
192
+ force: true
193
+ });
194
+ }
195
+ }
196
+ await run('emit');
197
+ await run('user-turn');
198
+ console.log('BOOKING COPILOT EVENT SEQUENCE CONCURRENCY PROOF: emit/user-turn multi-process sequence unique and strictly monotonic');
199
+ }
200
+
201
+
202
+ //# sourceURL=ts/scripts/booking-copilot-event-sequence-concurrency-proof-tests.ts
@@ -0,0 +1,102 @@
1
+ import assert from 'node:assert/strict';
2
+ import { validateActionReceiptV1 } from '../src/booking-surface/validation.js';
3
+ const receiptBase = {
4
+ schemaVersion: 'booking.surface.v1',
5
+ kind: 'action.receipt',
6
+ actionId: 'offers-query-gap-contract',
7
+ contextRef: 'ctx-gap-contract',
8
+ revision: 1
9
+ };
10
+ const ratesFailedResult = {
11
+ outcome: 'partial',
12
+ requestedCount: 1,
13
+ actualCount: 1,
14
+ hardCriteriaMet: true,
15
+ factRefs: [
16
+ 'hotel:hotel-1',
17
+ 'hotel:hotel-2',
18
+ 'offer:breakfast'
19
+ ]
20
+ };
21
+ const ratesFailedObservation = {
22
+ kind: 'offers.state',
23
+ hotelRefs: [
24
+ 'hotel-2'
25
+ ],
26
+ offerRefs: [
27
+ 'breakfast'
28
+ ],
29
+ loadedHotelCount: 1
30
+ };
31
+ const oldRatesFailedReceipt = {
32
+ ...receiptBase,
33
+ status: 'partial',
34
+ observation: ratesFailedObservation,
35
+ resultContract: {
36
+ ...ratesFailedResult,
37
+ gapCodes: [
38
+ 'hotel_rates_failed:hotel-1'
39
+ ]
40
+ }
41
+ };
42
+ const newRatesFailedReceipt = {
43
+ ...oldRatesFailedReceipt,
44
+ resultContract: {
45
+ ...ratesFailedResult,
46
+ gapCodes: [
47
+ 'hotel_rates_failed'
48
+ ]
49
+ }
50
+ };
51
+ assert.equal(validateActionReceiptV1(oldRatesFailedReceipt).ok, false, 'legacy dynamic supplier gap code must fail validation');
52
+ assert.deepEqual(validateActionReceiptV1(newRatesFailedReceipt), {
53
+ ok: true
54
+ }, 'fixed supplier gap code with hotel fact ref must validate');
55
+ assert.deepEqual(newRatesFailedReceipt.resultContract.factRefs, [
56
+ 'hotel:hotel-1',
57
+ 'hotel:hotel-2',
58
+ 'offer:breakfast'
59
+ ]);
60
+ const oldNotVisibleReceipt = {
61
+ ...receiptBase,
62
+ actionId: 'offers-query-hidden-hotel-gap-contract',
63
+ status: 'no_match',
64
+ observation: {
65
+ kind: 'offers.state',
66
+ hotelRefs: [],
67
+ offerRefs: [],
68
+ loadedHotelCount: 0
69
+ },
70
+ resultContract: {
71
+ outcome: 'empty',
72
+ requestedCount: 1,
73
+ actualCount: 0,
74
+ hardCriteriaMet: false,
75
+ factRefs: [
76
+ 'hotel:not-visible'
77
+ ],
78
+ gapCodes: [
79
+ 'hotel_not_visible:not-visible'
80
+ ]
81
+ }
82
+ };
83
+ const newNotVisibleReceipt = {
84
+ ...oldNotVisibleReceipt,
85
+ resultContract: {
86
+ ...oldNotVisibleReceipt.resultContract,
87
+ gapCodes: [
88
+ 'hotel_not_visible'
89
+ ]
90
+ }
91
+ };
92
+ assert.equal(validateActionReceiptV1(oldNotVisibleReceipt).ok, false, 'legacy dynamic visibility gap code must fail validation');
93
+ assert.deepEqual(validateActionReceiptV1(newNotVisibleReceipt), {
94
+ ok: true
95
+ }, 'fixed visibility gap code with hotel fact ref must validate');
96
+ assert.deepEqual(newNotVisibleReceipt.resultContract.factRefs, [
97
+ 'hotel:not-visible'
98
+ ]);
99
+ console.log('BOOKING COPILOT GAP CODE CONTRACT PROOF: dynamic legacy gap codes fail; fixed codes with hotel factRefs pass');
100
+
101
+
102
+ //# sourceURL=ts/scripts/booking-copilot-gap-code-contract-proof-tests.ts
@@ -0,0 +1,374 @@
1
+ import assert from 'node:assert/strict';
2
+ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { ensureLedger } from '../src/state-ledger.js';
8
+ import { BookingCopilotTaskRuntime } from '../src/booking-surface/runtime.js';
9
+ const taskId = 'task-operation-atomicity';
10
+ const workspace = {
11
+ schemaVersion: 'booking.surface.v1',
12
+ contextRef: 'ctx-operation-atomicity',
13
+ surface: 'tenant',
14
+ revision: 0,
15
+ locale: 'en-US',
16
+ currency: 'AED',
17
+ searchDraft: {},
18
+ results: {
19
+ status: 'idle'
20
+ },
21
+ visibleHotels: [],
22
+ loadedOffers: [],
23
+ shortlistedOfferRefs: [],
24
+ capabilities: {
25
+ surface: 'tenant',
26
+ allowedActions: [
27
+ 'search.patch',
28
+ 'search.run'
29
+ ]
30
+ }
31
+ };
32
+ const base = {
33
+ schemaVersion: 'booking.surface.v1',
34
+ kind: 'search.patch',
35
+ actionId: 'action-operation-atomicity',
36
+ contextRef: workspace.contextRef,
37
+ expectedRevision: 0,
38
+ reason: 'raw-reason-marker@example.invalid Bearer RAW_SECRET',
39
+ factRefs: [
40
+ 'fact:opaque'
41
+ ],
42
+ input: {
43
+ patch: {
44
+ destination: {
45
+ query: 'raw-input-marker@example.invalid'
46
+ }
47
+ }
48
+ }
49
+ };
50
+ const child = process.argv.indexOf('--child') >= 0;
51
+ const replayMode = process.argv.indexOf('--replay') >= 0;
52
+ const root = child ? process.argv[process.argv.indexOf('--child') + 1] : '';
53
+ const variant = child ? process.argv[process.argv.indexOf('--child') + 2] : '';
54
+ if (replayMode) {
55
+ const replayRoot = process.argv[process.argv.indexOf('--replay') + 1];
56
+ const replayVariant = process.argv[process.argv.indexOf('--replay') + 2] ?? 'base';
57
+ const replayLedger = ensureLedger(replayRoot);
58
+ const replayAction = replayVariant === 'conflict' ? action('conflict') : replayVariant === 'different' ? action('different') : base;
59
+ try {
60
+ console.log(JSON.stringify({
61
+ result: 'success',
62
+ event: new BookingCopilotTaskRuntime(replayLedger).issueOperation(taskId, replayAction)
63
+ }));
64
+ } catch (error) {
65
+ console.log(JSON.stringify({
66
+ result: 'error',
67
+ error: error instanceof Error ? error.message : String(error)
68
+ }));
69
+ }
70
+ replayLedger.close();
71
+ process.exit(0);
72
+ }
73
+ function waitFile(path, timeout = 30_000) {
74
+ return new Promise((resolve, reject)=>{
75
+ const end = Date.now() + timeout;
76
+ const tick = ()=>existsSync(path) ? resolve() : Date.now() > end ? reject(new Error(`timeout:${path}`)) : setTimeout(tick, 5);
77
+ tick();
78
+ });
79
+ }
80
+ function action(which) {
81
+ return {
82
+ ...base,
83
+ kind: which === 'kind' ? 'search.run' : base.kind,
84
+ reason: which === 'conflict' ? 'different reason' : base.reason,
85
+ contextRef: which === 'context' ? 'ctx-other' : base.contextRef,
86
+ expectedRevision: which === 'revision' ? 1 : base.expectedRevision,
87
+ factRefs: which === 'fact-order' ? [
88
+ 'fact:other',
89
+ 'fact:opaque'
90
+ ] : base.factRefs,
91
+ input: which === 'kind' ? {} : which === 'input' ? {
92
+ patch: {
93
+ destination: {
94
+ query: 'other'
95
+ }
96
+ }
97
+ } : base.input,
98
+ actionId: which === 'different' ? 'action-different' : base.actionId
99
+ };
100
+ }
101
+ if (child) {
102
+ const ledger = ensureLedger(root);
103
+ const runtime = new BookingCopilotTaskRuntime(ledger);
104
+ const original = runtime.resumeTask.bind(runtime);
105
+ let first = true;
106
+ writeFileSync(join(root, `started-${variant}`), 'started');
107
+ const gateEnd = Date.now() + 10_000;
108
+ while(!existsSync(join(root, 'start'))){
109
+ if (Date.now() > gateEnd) throw new Error('start_gate_timeout');
110
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
111
+ }
112
+ if (variant !== 'left') Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
113
+ runtime.resumeTask = (id)=>{
114
+ const state = original(id);
115
+ if (first && !state?.pendingAction) {
116
+ first = false;
117
+ writeFileSync(join(root, `ready-${variant}`), 'ready');
118
+ const end = Date.now() + 10_000;
119
+ while(!existsSync(join(root, 'release'))){
120
+ if (Date.now() > end) throw new Error('barrier_timeout');
121
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
122
+ }
123
+ }
124
+ return state;
125
+ };
126
+ try {
127
+ const event = runtime.issueOperation(taskId, action(variant));
128
+ console.log(JSON.stringify({
129
+ result: 'success',
130
+ event
131
+ }));
132
+ } catch (error) {
133
+ console.log(JSON.stringify({
134
+ result: 'error',
135
+ error: error instanceof Error ? error.message : String(error)
136
+ }));
137
+ }
138
+ ledger.close();
139
+ process.exit(0);
140
+ }
141
+ async function pair(root, left, right) {
142
+ const tsx = fileURLToPath(new URL('../node_modules/tsx/dist/cli.mjs', import.meta.url));
143
+ const script = fileURLToPath(import.meta.url);
144
+ const ps = [
145
+ left,
146
+ right
147
+ ].map((v)=>spawn(process.execPath, [
148
+ tsx,
149
+ script,
150
+ '--child',
151
+ root,
152
+ v
153
+ ], {
154
+ cwd: process.cwd(),
155
+ stdio: [
156
+ 'ignore',
157
+ 'pipe',
158
+ 'pipe'
159
+ ]
160
+ }));
161
+ const out = ps.map(()=>'');
162
+ ps.forEach((p, i)=>{
163
+ p.stdout.on('data', (b)=>{
164
+ out[i] += b;
165
+ });
166
+ p.stderr.on('data', (b)=>{
167
+ out[i] += b;
168
+ });
169
+ });
170
+ await Promise.all([
171
+ waitFile(join(root, `started-${left}`)),
172
+ waitFile(join(root, `started-${right}`))
173
+ ]);
174
+ writeFileSync(join(root, 'start'), 'start');
175
+ try {
176
+ await Promise.race([
177
+ waitFile(join(root, `ready-${left}`)),
178
+ waitFile(join(root, `ready-${right}`))
179
+ ]);
180
+ } catch (error) {
181
+ ps.forEach((p)=>p.kill('SIGKILL'));
182
+ throw new Error(`workers_did_not_reach_callsite:${out.join('|')}:${error}`);
183
+ }
184
+ await new Promise((resolve)=>setTimeout(resolve, 250));
185
+ assert.equal(existsSync(join(root, `ready-${left}`)) && existsSync(join(root, `ready-${right}`)), false, 'both workers entered transaction before release');
186
+ writeFileSync(join(root, 'release'), 'release');
187
+ try {
188
+ await Promise.all(ps.map((p)=>new Promise((resolve, reject)=>{
189
+ const timer = setTimeout(()=>reject(new Error('worker_timeout')), 10_000);
190
+ p.on('exit', (code)=>{
191
+ clearTimeout(timer);
192
+ code === 0 ? resolve() : reject(new Error(`worker_exit:${code}:${out.join('|')}`));
193
+ });
194
+ })));
195
+ } finally{
196
+ ps.forEach((p)=>{
197
+ if (p.exitCode === null) p.kill('SIGKILL');
198
+ });
199
+ }
200
+ return out.map((s)=>JSON.parse(s.trim().split('\n').at(-1)));
201
+ }
202
+ async function run(kind, conflictVariant = 'conflict') {
203
+ const root = mkdtempSync(join(tmpdir(), `gotry-operation-${kind}-`));
204
+ mkdirSync(root, {
205
+ recursive: true
206
+ });
207
+ const ledger = ensureLedger(root);
208
+ const runtime = new BookingCopilotTaskRuntime(ledger);
209
+ const turn = {
210
+ schemaVersion: 'booking.surface.v1',
211
+ kind: 'user.turn',
212
+ taskId,
213
+ workspace,
214
+ request: {
215
+ text: 'opaque'
216
+ }
217
+ };
218
+ runtime.startTask(turn);
219
+ const result = await pair(root, kind === 'different' ? 'different' : 'left', kind === 'conflict' ? conflictVariant : 'right');
220
+ const success = result.filter((r)=>r.result === 'success');
221
+ assert.equal(success.length, kind === 'same' ? 2 : 1);
222
+ if (kind === 'conflict') assert.equal(result.filter((r)=>r.error === 'action_conflict').length, 1, JSON.stringify(result));
223
+ if (kind === 'different') assert.equal(result.filter((r)=>r.error === 'receipt_required').length, 1);
224
+ if (kind === 'same') assert.deepEqual(success[0].event, success[1].event);
225
+ assert.equal(ledger.db.prepare("SELECT COUNT(*) AS n FROM events WHERE kind = 'booking.copilot.action.issued'").get().n, 1);
226
+ const durable = ledger.db.prepare("SELECT payload FROM events WHERE kind = 'booking.copilot.action.issued'").get();
227
+ assert.ok(!/raw-reason-marker|raw-input-marker|Bearer RAW_SECRET|@example|secret/i.test(durable.payload));
228
+ if (kind === 'same') {
229
+ const event = success[0].event;
230
+ ledger.close();
231
+ const tsx = fileURLToPath(new URL('../node_modules/tsx/dist/cli.mjs', import.meta.url));
232
+ async function replayVariant(v) {
233
+ const replayChild = spawn(process.execPath, [
234
+ tsx,
235
+ fileURLToPath(import.meta.url),
236
+ '--replay',
237
+ root,
238
+ v
239
+ ], {
240
+ cwd: process.cwd(),
241
+ stdio: [
242
+ 'ignore',
243
+ 'pipe',
244
+ 'pipe'
245
+ ]
246
+ });
247
+ let replayOutput = '';
248
+ replayChild.stdout.on('data', (b)=>{
249
+ replayOutput += b;
250
+ });
251
+ replayChild.stderr.on('data', (b)=>{
252
+ replayOutput += b;
253
+ });
254
+ await new Promise((resolve, reject)=>{
255
+ const timer = setTimeout(()=>{
256
+ replayChild.kill('SIGKILL');
257
+ reject(new Error('replay_timeout'));
258
+ }, 10_000);
259
+ replayChild.on('exit', (code)=>{
260
+ clearTimeout(timer);
261
+ code === 0 ? resolve() : reject(new Error(`replay_exit:${code}:${replayOutput}`));
262
+ });
263
+ });
264
+ return JSON.parse(replayOutput.trim().split('\n').at(-1));
265
+ }
266
+ const replayResult = await replayVariant('base');
267
+ assert.deepEqual(replayResult.event, event);
268
+ const conflictResult = await replayVariant('conflict');
269
+ assert.equal(conflictResult.error, 'action_conflict');
270
+ const differentResult = await replayVariant('different');
271
+ assert.equal(differentResult.error, 'receipt_required');
272
+ const restarted = ensureLedger(root);
273
+ assert.equal(restarted.db.prepare("SELECT COUNT(*) AS n FROM events WHERE kind = 'booking.copilot.action.issued'").get().n, 1);
274
+ const replay = replayResult.event;
275
+ const pending = new BookingCopilotTaskRuntime(restarted).resumeTask(taskId)?.pendingAction;
276
+ assert.deepEqual(pending && {
277
+ eventId: pending.eventId,
278
+ sequence: pending.sequence,
279
+ emittedAt: pending.emittedAt
280
+ }, {
281
+ eventId: replay.eventId,
282
+ sequence: replay.sequence,
283
+ emittedAt: replay.emittedAt
284
+ });
285
+ restarted.close();
286
+ } else {
287
+ ledger.close();
288
+ }
289
+ rmSync(root, {
290
+ recursive: true,
291
+ force: true
292
+ });
293
+ }
294
+ if (!child) {
295
+ for(let round = 0; round < 20; round++){
296
+ await run('same');
297
+ await run('conflict', [
298
+ 'conflict',
299
+ 'kind',
300
+ 'context',
301
+ 'revision',
302
+ 'fact-order',
303
+ 'input'
304
+ ][round % 6]);
305
+ await run('different');
306
+ }
307
+ const unsafeRoot = mkdtempSync(join(tmpdir(), 'gotry-operation-unsafe-'));
308
+ const unsafeLedger = ensureLedger(unsafeRoot);
309
+ const unsafeRuntime = new BookingCopilotTaskRuntime(unsafeLedger);
310
+ unsafeRuntime.startTask({
311
+ schemaVersion: 'booking.surface.v1',
312
+ kind: 'user.turn',
313
+ taskId,
314
+ workspace,
315
+ request: {
316
+ text: 'opaque'
317
+ }
318
+ });
319
+ assert.throws(()=>unsafeRuntime.issueOperation(taskId, {
320
+ ...base,
321
+ factRefs: [
322
+ 'attacker@example.invalid'
323
+ ]
324
+ }), /unsafe_fact_ref/);
325
+ assert.throws(()=>unsafeRuntime.issueOperation(taskId, {
326
+ ...base,
327
+ actionId: 'attacker@example.invalid'
328
+ }), /unsafe_action_id/);
329
+ assert.throws(()=>unsafeRuntime.issueOperation(taskId, {
330
+ ...base,
331
+ actionId: 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.signature'
332
+ }), /unsafe_action_id/);
333
+ assert.equal(unsafeLedger.countEvents(), 2);
334
+ unsafeLedger.close();
335
+ rmSync(unsafeRoot, {
336
+ recursive: true,
337
+ force: true
338
+ });
339
+ for (const field of [
340
+ 'actionId',
341
+ 'factRefs',
342
+ 'expectedRevision'
343
+ ]){
344
+ const tamperRoot = mkdtempSync(join(tmpdir(), `gotry-operation-tamper-${field}-`));
345
+ const tamperLedger = ensureLedger(tamperRoot);
346
+ const tamperRuntime = new BookingCopilotTaskRuntime(tamperLedger);
347
+ tamperRuntime.startTask({
348
+ schemaVersion: 'booking.surface.v1',
349
+ kind: 'user.turn',
350
+ taskId,
351
+ workspace,
352
+ request: {
353
+ text: 'opaque'
354
+ }
355
+ });
356
+ tamperRuntime.issueOperation(taskId, base);
357
+ const row = tamperLedger.db.prepare("SELECT seq, payload FROM events WHERE kind = 'booking.copilot.action.issued'").get();
358
+ const payload = JSON.parse(row.payload);
359
+ payload.action[field] = field === 'factRefs' ? [
360
+ 'tampered'
361
+ ] : field === 'expectedRevision' ? 99 : 'tampered-action';
362
+ tamperLedger.db.prepare('UPDATE events SET payload = ? WHERE seq = ?').run(JSON.stringify(payload), row.seq);
363
+ assert.throws(()=>new BookingCopilotTaskRuntime(tamperLedger).resumeTask(taskId), /ledger_corrupt/);
364
+ tamperLedger.close();
365
+ rmSync(tamperRoot, {
366
+ recursive: true,
367
+ force: true
368
+ });
369
+ }
370
+ console.log('BOOKING COPILOT OPERATION LEDGER CONCURRENCY PROOF: 20 rounds atomic');
371
+ }
372
+
373
+
374
+ //# sourceURL=ts/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.ts