@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -2,37 +2,32 @@ import { readFileSync } from 'node:fs';
2
2
  import { Ajv2020 } from 'ajv/dist/2020.js';
3
3
  import { BOOKING_READ_ACTION_KINDS, BOOKING_BLOCKER_CODES, BOOKING_GAP_CODES } from './contracts.js';
4
4
  const schema = JSON.parse(readFileSync(new URL('../../../schemas/booking.surface.schema.json', import.meta.url), 'utf8'));
5
- const ajv = new Ajv2020({
6
- allErrors: true,
7
- strict: true
8
- });
5
+ /** Canonical schema bytes (single source of truth) for prompt/tool derivations elsewhere. */
6
+ export const bookingSurfaceSchema = schema;
7
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
9
8
  ajv.addSchema(schema);
10
9
  const validate = ajv.compile(schema);
11
- const validateActionCanonical = ajv.compile({
12
- $ref: `${schema.$id}#/$defs/Action`
13
- });
14
- const validateBlockerCanonical = ajv.compile({
15
- $ref: `${schema.$id}#/$defs/Blocker`
16
- });
17
- const validateApprovalCanonical = ajv.compile({
18
- $ref: `${schema.$id}#/$defs/Approval`
19
- });
20
- const validateEventCanonical = ajv.compile({
21
- $ref: `${schema.$id}#/$defs/Event`
22
- });
10
+ const validateActionCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Action` });
11
+ const validateBlockerCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Blocker` });
12
+ const validateApprovalCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Approval` });
13
+ const validateEventCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Event` });
23
14
  const kinds = new Set(BOOKING_READ_ACTION_KINDS);
24
15
  const codes = new Set(BOOKING_BLOCKER_CODES);
25
16
  const gapCodes = new Set(BOOKING_GAP_CODES);
26
- const safeOpaque = (value)=>typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(value);
27
- const safeRequestKey = (value)=>typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{7,127}$/.test(value) && !secret.test(value);
17
+ const safeOpaque = (value) => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(value);
18
+ const safeRequestKey = (value) => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{7,127}$/.test(value) && !secret.test(value);
28
19
  const secret = /(?:Bearer\s+\S|\bsk-(?!ill(?:s)?\b)[A-Za-z0-9_-]{12,}|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/i;
20
+ // `.example` is reserved for documentation, so these identifiers cannot be a
21
+ // real mailbox. Do not allowlist lookalikes on routable domains.
29
22
  const technicalEmail = /^(?:schema|version)@[a-z0-9-]+\.example$/i;
30
23
  const currencyAmount = /\b(?:[A-Z]{3}\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?\s*[A-Z]{3})\b/;
31
24
  const rfc3339DateTime = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|([+-])(\d{2}):(\d{2}))$/;
32
25
  export function isBookingDateTime(value) {
33
- if (typeof value !== 'string') return false;
26
+ if (typeof value !== 'string')
27
+ return false;
34
28
  const match = rfc3339DateTime.exec(value);
35
- if (!match) return false;
29
+ if (!match)
30
+ return false;
36
31
  const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
37
32
  const year = Number(yearText);
38
33
  const month = Number(monthText);
@@ -42,9 +37,11 @@ export function isBookingDateTime(value) {
42
37
  const second = Number(secondText);
43
38
  const offsetHour = match[9] === undefined ? 0 : Number(match[9]);
44
39
  const offsetMinute = match[10] === undefined ? 0 : Number(match[10]);
45
- if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) return false;
40
+ if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59)
41
+ return false;
46
42
  const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
47
- if (day < 1 || day > daysInMonth) return false;
43
+ if (day < 1 || day > daysInMonth)
44
+ return false;
48
45
  return !Number.isNaN(Date.parse(value));
49
46
  }
50
47
  function normalizedWords(value) {
@@ -53,20 +50,24 @@ function normalizedWords(value) {
53
50
  function hasSupplierCost(value) {
54
51
  const normalized = normalizedWords(value);
55
52
  const lower = normalized.toLowerCase();
56
- return /\b(?:supplier|vendor|internal)\b/.test(lower) && /\b(?:cost|rate|price)\b/.test(lower) && currencyAmount.test(normalized.toUpperCase());
53
+ return (/\b(?:supplier|vendor|internal)\b/.test(lower) &&
54
+ /\b(?:cost|rate|price)\b/.test(lower) &&
55
+ currencyAmount.test(normalized.toUpperCase()));
57
56
  }
58
57
  function hasInternationalPhone(value) {
59
58
  const normalized = value.normalize('NFKC');
60
- for (const plusIndex of normalized.split('').flatMap((char, index)=>char === '+' ? [
61
- index
62
- ] : [])){
59
+ for (const plusIndex of normalized.split('').flatMap((char, index) => char === '+' ? [index] : [])) {
63
60
  let digits = '';
64
- for (const char of normalized.slice(plusIndex + 1, plusIndex + 48)){
65
- if (/\d/u.test(char)) digits += char;
66
- else if (/\p{L}|@/u.test(char)) break;
67
- if (digits.length > 15) break;
61
+ for (const char of normalized.slice(plusIndex + 1, plusIndex + 48)) {
62
+ if (/\d/u.test(char))
63
+ digits += char;
64
+ else if (/\p{L}|@/u.test(char))
65
+ break;
66
+ if (digits.length > 15)
67
+ break;
68
68
  }
69
- if (/^[1-9]\d{7,14}$/.test(digits)) return true;
69
+ if (/^[1-9]\d{7,14}$/.test(digits))
70
+ return true;
70
71
  }
71
72
  return false;
72
73
  }
@@ -74,246 +75,177 @@ function hasPaymentCard(value) {
74
75
  const normalized = value.normalize('NFKC');
75
76
  const candidates = normalized.match(/(?:\d[ -]?){12,18}\d/g) ?? [];
76
77
  const explicitlyLabeled = /\b(?:card|payment|visa|mastercard)\b/i.test(normalized);
77
- return candidates.some((candidate)=>(explicitlyLabeled || /[ -]/.test(candidate)) && luhn(candidate));
78
+ return candidates.some((candidate) => (explicitlyLabeled || /[ -]/.test(candidate)) && luhn(candidate));
78
79
  }
79
80
  function hasSensitiveText(value, opts = {}) {
80
81
  const normalized = value.normalize('NFKC');
81
- if (normalized.includes('@') && !technicalEmail.test(normalized.trim())) return true;
82
- return secret.test(normalized) || hasInternationalPhone(normalized) || hasSupplierCost(normalized) || !opts.allowOpaqueNumericId && hasPaymentCard(normalized);
83
- }
84
- function luhn(value) {
85
- const digits = value.replaceAll(/\D/g, '');
86
- if (digits.length < 13 || digits.length > 19) return false;
87
- let sum = 0;
88
- for(let i = digits.length - 1, parity = 0; i >= 0; i--, parity++){
89
- let n = Number(digits[i]);
90
- if (parity % 2 === 1) {
91
- n *= 2;
92
- if (n > 9) n -= 9;
93
- }
94
- sum += n;
82
+ if (normalized.includes('@') && !technicalEmail.test(normalized.trim()))
83
+ return true;
84
+ return secret.test(normalized) || hasInternationalPhone(normalized) || hasSupplierCost(normalized) || (!opts.allowOpaqueNumericId && hasPaymentCard(normalized));
85
+ }
86
+ function luhn(value) { const digits = value.replaceAll(/\D/g, ''); if (digits.length < 13 || digits.length > 19)
87
+ return false; let sum = 0; for (let i = digits.length - 1, parity = 0; i >= 0; i--, parity++) {
88
+ let n = Number(digits[i]);
89
+ if (parity % 2 === 1) {
90
+ n *= 2;
91
+ if (n > 9)
92
+ n -= 9;
95
93
  }
96
- return sum % 10 === 0;
97
- }
94
+ sum += n;
95
+ } return sum % 10 === 0; }
98
96
  const opaqueFieldNames = new Set([
99
- 'eventId',
100
- 'taskId',
101
- 'contextRef',
102
- 'actionId',
103
- 'hotelRef',
104
- 'offerRef',
105
- 'offerVersionRef',
106
- 'checkedOfferVersionRef',
107
- 'currentOfferVersionRef',
108
- 'verifiedOfferRef',
109
- 'orderRef',
110
- 'searchSessionRef',
111
- 'handoffRef',
112
- 'questionId',
113
- 'blockerId',
114
- 'approvalId',
115
- 'sourceActionId',
116
- 'targetActionId',
117
- 'sourceTurnId',
118
- 'turnId',
119
- 'requestKey',
120
- 'taskHandle',
121
- 'nonce',
122
- 'deliveryNonce',
123
- 'optionDigest',
124
- 'sourceReceiptDigest',
125
- 'valueDigest'
97
+ 'eventId', 'taskId', 'contextRef', 'actionId', 'hotelRef', 'offerRef', 'offerVersionRef', 'checkedOfferVersionRef', 'currentOfferVersionRef', 'verifiedOfferRef',
98
+ 'orderRef', 'searchSessionRef', 'handoffRef', 'questionId', 'blockerId', 'approvalId',
99
+ 'sourceActionId', 'targetActionId', 'sourceTurnId', 'turnId', 'requestKey', 'taskHandle',
100
+ 'nonce', 'deliveryNonce', 'optionDigest', 'sourceReceiptDigest', 'valueDigest',
126
101
  ]);
127
102
  function isOpaqueFieldPath(path) {
128
103
  const clean = path.replace(/\[\]$/g, '');
129
104
  const field = clean.split('.').at(-1);
130
105
  return field ? opaqueFieldNames.has(field) || field.endsWith('Ref') || field.endsWith('Refs') : false;
131
106
  }
132
- const scan = (v, path = '$')=>typeof v === 'string' ? hasSensitiveText(v, {
133
- allowOpaqueNumericId: isOpaqueFieldPath(path)
134
- }) : Array.isArray(v) ? v.some((child)=>scan(child, `${path}[]`)) : !!v && typeof v === 'object' ? Object.entries(v).some(([k, x])=>/cost|price/i.test(k) ? /supplier|internal|wholesale/i.test(String(x)) : scan(x, `${path}.${k}`)) : false;
135
- export function isSafeBookingRequestKey(value) {
136
- return safeRequestKey(value);
137
- }
138
- function errors(v) {
139
- return (v.errors ?? []).map((e)=>`${e.instancePath || '$'}: ${e.message ?? e.keyword}`);
140
- }
107
+ const scan = (v, path = '$') => typeof v === 'string'
108
+ ? hasSensitiveText(v, { allowOpaqueNumericId: isOpaqueFieldPath(path) })
109
+ : Array.isArray(v)
110
+ ? v.some((child) => scan(child, `${path}[]`))
111
+ : !!v && typeof v === 'object'
112
+ ? Object.entries(v).some(([k, x]) => /cost|price/i.test(k) ? /supplier|internal|wholesale/i.test(String(x)) : scan(x, `${path}.${k}`))
113
+ : false;
114
+ export function isSafeBookingRequestKey(value) { return safeRequestKey(value); }
115
+ function errors(v) { return (v.errors ?? []).map(e => `${e.instancePath || '$'}: ${e.message ?? e.keyword}`); }
141
116
  function workspaceErrors(workspace, path) {
142
117
  const e = [];
143
- if (!workspace || typeof workspace !== 'object' || Array.isArray(workspace)) return e;
118
+ if (!workspace || typeof workspace !== 'object' || Array.isArray(workspace))
119
+ return e;
144
120
  const loadedOffers = Array.isArray(workspace.loadedOffers) ? workspace.loadedOffers : [];
145
- const offerRefs = loadedOffers.map((offer)=>offer?.offerRef).filter((ref)=>typeof ref === 'string');
146
- const offerVersionRefs = loadedOffers.map((offer)=>offer?.offerVersionRef).filter((ref)=>typeof ref === 'string');
147
- if (new Set(offerRefs).size !== offerRefs.length) e.push(`${path}.loadedOffers: duplicate offerRef`);
148
- if (new Set(offerVersionRefs).size !== offerVersionRefs.length) e.push(`${path}.loadedOffers: duplicate offerVersionRef`);
121
+ const offerRefs = loadedOffers.map((offer) => offer?.offerRef).filter((ref) => typeof ref === 'string');
122
+ const offerVersionRefs = loadedOffers.map((offer) => offer?.offerVersionRef).filter((ref) => typeof ref === 'string');
123
+ if (new Set(offerRefs).size !== offerRefs.length)
124
+ e.push(`${path}.loadedOffers: duplicate offerRef`);
125
+ if (new Set(offerVersionRefs).size !== offerVersionRefs.length)
126
+ e.push(`${path}.loadedOffers: duplicate offerVersionRef`);
149
127
  const verified = workspace.verifiedOffer;
150
128
  if (verified !== undefined) {
151
- if (!loadedOffers.some((offer)=>offer?.offerRef === verified.offerRef && offer?.offerVersionRef === verified.offerVersionRef)) e.push(`${path}.verifiedOffer: loaded offer version required`);
152
- if (!safeOpaque(verified.verifiedOfferRef) || !isBookingDateTime(verified.expiresAt)) e.push(`${path}.verifiedOffer: safe ref and parseable expiry required`);
129
+ if (!loadedOffers.some((offer) => offer?.offerRef === verified.offerRef && offer?.offerVersionRef === verified.offerVersionRef))
130
+ e.push(`${path}.verifiedOffer: loaded offer version required`);
131
+ if (!safeOpaque(verified.verifiedOfferRef) || !isBookingDateTime(verified.expiresAt))
132
+ e.push(`${path}.verifiedOffer: safe ref and parseable expiry required`);
153
133
  }
154
134
  return e;
155
135
  }
156
136
  function cross(value) {
157
137
  const e = [];
158
- if (scan(value)) e.push('$: unsafe PII/payment/supplier-cost/credential text');
159
- if (!value || typeof value !== 'object' || Array.isArray(value)) return e;
138
+ if (scan(value))
139
+ e.push('$: unsafe PII/payment/supplier-cost/credential text');
140
+ if (!value || typeof value !== 'object' || Array.isArray(value))
141
+ return e;
160
142
  const x = value;
161
143
  const semantic = x.kind === 'action.receipt.continuation' ? x.receipt : x;
162
- if (x.workspace && x.kind !== 'user.turn.ingress') e.push(...workspaceErrors(x.workspace, '$.workspace'));
163
- if (x.schemaVersion === 'booking.surface' && Array.isArray(x.loadedOffers)) e.push(...workspaceErrors(x, '$'));
144
+ if (x.workspace && x.kind !== 'user.turn.ingress')
145
+ e.push(...workspaceErrors(x.workspace, '$.workspace'));
146
+ if (x.schemaVersion === 'booking.surface' && Array.isArray(x.loadedOffers))
147
+ e.push(...workspaceErrors(x, '$'));
164
148
  if (x.kind === 'user.turn.ingress') {
165
- if (!safeRequestKey(x.requestKey)) e.push('$.requestKey: safe opaque request key required');
149
+ if (!safeRequestKey(x.requestKey))
150
+ e.push('$.requestKey: safe opaque request key required');
166
151
  const workspace = x.workspace;
167
152
  e.push(...workspaceErrors(workspace, '$.workspace'));
168
153
  const visibleHotels = Array.isArray(workspace?.visibleHotels) ? workspace.visibleHotels : [];
169
154
  const loadedOffers = Array.isArray(workspace?.loadedOffers) ? workspace.loadedOffers : [];
170
- if (workspace?.focusedHotelRef !== undefined && !visibleHotels.some((hotel)=>hotel?.hotelRef === workspace.focusedHotelRef)) e.push('$.workspace.focusedHotelRef: visible hotel required');
171
- const selectedOffer = workspace?.selectedOfferRef === undefined ? undefined : loadedOffers.find((offer)=>offer?.offerRef === workspace.selectedOfferRef);
172
- if (workspace?.selectedOfferRef !== undefined && !selectedOffer) e.push('$.workspace.selectedOfferRef: loaded offer required');
173
- if (selectedOffer && !visibleHotels.some((hotel)=>hotel?.hotelRef === selectedOffer.hotelRef)) e.push('$.workspace.selectedOfferRef: offer hotel must be visible');
155
+ if (workspace?.focusedHotelRef !== undefined && !visibleHotels.some((hotel) => hotel?.hotelRef === workspace.focusedHotelRef))
156
+ e.push('$.workspace.focusedHotelRef: visible hotel required');
157
+ const selectedOffer = workspace?.selectedOfferRef === undefined ? undefined : loadedOffers.find((offer) => offer?.offerRef === workspace.selectedOfferRef);
158
+ if (workspace?.selectedOfferRef !== undefined && !selectedOffer)
159
+ e.push('$.workspace.selectedOfferRef: loaded offer required');
160
+ if (selectedOffer && !visibleHotels.some((hotel) => hotel?.hotelRef === selectedOffer.hotelRef))
161
+ e.push('$.workspace.selectedOfferRef: offer hotel must be visible');
174
162
  }
175
- if (semantic !== x && semantic?.resultContract?.hardCriteriaMet === true && semantic.resultContract.blockers?.length) e.push('$.receipt.resultContract: hardCriteriaMet cannot coexist with blockers');
163
+ if (semantic !== x && semantic?.resultContract?.hardCriteriaMet === true && semantic.resultContract.blockers?.length)
164
+ e.push('$.receipt.resultContract: hardCriteriaMet cannot coexist with blockers');
176
165
  if (kinds.has(x.kind)) {
177
166
  const allowed = {
178
- 'search.patch': [
179
- 'patch'
180
- ],
181
- 'search.run': [],
182
- 'results.view.patch': [
183
- 'patch'
184
- ],
185
- 'hotel.focus': [
186
- 'hotelRef'
187
- ],
188
- 'hotel.select': [
189
- 'hotelRef'
190
- ],
191
- 'offers.query': [
192
- 'hotelRefs',
193
- 'criteria'
194
- ],
195
- 'offers.view.patch': [
196
- 'hotelRef',
197
- 'criteria'
198
- ],
199
- 'offers.compare': [
200
- 'offerRefs',
201
- 'requestedCount'
202
- ],
203
- 'offer.select': [
204
- 'offerRef',
205
- 'offerVersionRef'
206
- ],
207
- 'offer.check': [
208
- 'offerRef',
209
- 'offerVersionRef'
210
- ],
211
- 'checkout.prepare': [
212
- 'offerRef',
213
- 'offerVersionRef',
214
- 'verifiedOfferRef'
215
- ],
216
- 'order.observe': [
217
- 'orderRef'
218
- ]
167
+ 'search.patch': ['patch'], 'search.run': [], 'results.view.patch': ['patch'],
168
+ 'hotel.focus': ['hotelRef'], 'hotel.select': ['hotelRef'],
169
+ 'offers.query': ['hotelRefs', 'criteria'], 'offers.view.patch': ['hotelRef', 'criteria'],
170
+ 'offers.compare': ['offerRefs', 'requestedCount'], 'offer.select': ['offerRef', 'offerVersionRef'],
171
+ 'offer.check': ['offerRef', 'offerVersionRef'], 'checkout.prepare': ['offerRef', 'offerVersionRef', 'verifiedOfferRef'], 'order.observe': ['orderRef'],
219
172
  };
220
173
  const input = x.input;
221
- if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some((k)=>!allowed[x.kind].includes(k))) e.push('$.input: closed kind-specific input required');
174
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(k => !allowed[x.kind].includes(k)))
175
+ e.push('$.input: closed kind-specific input required');
222
176
  }
223
- if (x.kind === 'action.receipt' && x.resultContract?.hardCriteriaMet === true && x.resultContract.blockers?.length) e.push('$.resultContract: hardCriteriaMet cannot coexist with blockers');
224
- if (x.kind === 'user.turn' && Array.isArray(x.request?.approval)) e.push('$.request.approval: at most one approval');
177
+ if (x.kind === 'action.receipt' && x.resultContract?.hardCriteriaMet === true && x.resultContract.blockers?.length)
178
+ e.push('$.resultContract: hardCriteriaMet cannot coexist with blockers');
179
+ if (x.kind === 'user.turn' && Array.isArray(x.request?.approval))
180
+ e.push('$.request.approval: at most one approval');
225
181
  if (x.kind === 'action.receipt' || x.kind === 'action.receipt.continuation') {
226
182
  const r = x.kind === 'action.receipt.continuation' ? x.receipt : x;
227
- if (r?.resultContract?.gapCodes?.some((code)=>!gapCodes.has(String(code)))) e.push('$.resultContract.gapCodes: unregistered gap code');
183
+ if (r?.resultContract?.gapCodes?.some((code) => !gapCodes.has(String(code))))
184
+ e.push('$.resultContract.gapCodes: unregistered gap code');
228
185
  const observation = r?.observation;
229
186
  if (observation?.kind === 'offer.availability') {
230
- if (r.status === 'applied' && (observation.available !== true || !observation.verifiedOfferRef || observation.currentOfferVersionRef !== observation.checkedOfferVersionRef || observation.changedFactRefs?.length || observation.gapCodes?.length)) e.push('$.observation: applied availability requires unchanged checked/current version, verified ref, and no gaps');
231
- if (r.status === 'applied' && (r.resultContract?.outcome !== 'complete' || r.resultContract?.hardCriteriaMet !== true || r.resultContract?.gapCodes?.length || r.resultContract?.blockers?.length)) e.push('$.resultContract: applied availability requires complete hard-criteria result without gaps or blockers');
232
- if (r.status === 'changed' && (observation.available !== true || !observation.currentOfferVersionRef || observation.currentOfferVersionRef === observation.checkedOfferVersionRef || !observation.changedFactRefs?.length || observation.verifiedOfferRef)) e.push('$.observation: changed availability requires available replacement current version without verified ref');
233
- if ((r.status === 'unavailable' || r.status === 'no_match') && (observation.available !== false || observation.currentOfferVersionRef || observation.verifiedOfferRef)) e.push('$.observation: negative availability must not expose current or verified version');
234
- if (r.status !== 'applied' && observation.verifiedOfferRef) e.push('$.observation: only applied availability may expose verified ref');
187
+ if (r.status === 'applied' && (observation.available !== true || !observation.verifiedOfferRef || observation.currentOfferVersionRef !== observation.checkedOfferVersionRef || observation.changedFactRefs?.length || observation.gapCodes?.length))
188
+ e.push('$.observation: applied availability requires unchanged checked/current version, verified ref, and no gaps');
189
+ if (r.status === 'applied' && (r.resultContract?.outcome !== 'complete' || r.resultContract?.hardCriteriaMet !== true || r.resultContract?.gapCodes?.length || r.resultContract?.blockers?.length))
190
+ e.push('$.resultContract: applied availability requires complete hard-criteria result without gaps or blockers');
191
+ if (r.status === 'changed' && (observation.available !== true || !observation.currentOfferVersionRef || observation.currentOfferVersionRef === observation.checkedOfferVersionRef || !observation.changedFactRefs?.length || observation.verifiedOfferRef))
192
+ e.push('$.observation: changed availability requires available replacement current version without verified ref');
193
+ if ((r.status === 'unavailable' || r.status === 'no_match') && (observation.available !== false || observation.currentOfferVersionRef || observation.verifiedOfferRef))
194
+ e.push('$.observation: negative availability must not expose current or verified version');
195
+ if (r.status !== 'applied' && observation.verifiedOfferRef)
196
+ e.push('$.observation: only applied availability may expose verified ref');
235
197
  }
236
198
  }
237
199
  const blocker = x.blocker ?? x.question?.blocker;
238
- if (blocker && !/^(searchDraft|results|offers|checkout)(\.[A-Za-z][A-Za-z0-9_]*){0,3}$/.test(blocker.criterionPath ?? '')) e.push('$.blocker.criterionPath: closed criterion path required');
239
- if (blocker?.evidence?.factRefs && new Set(blocker.evidence.factRefs).size !== blocker.evidence.factRefs.length) e.push('$.blocker.evidence.factRefs: unique refs required');
200
+ if (blocker && !/^(searchDraft|results|offers|checkout)(\.[A-Za-z][A-Za-z0-9_]*){0,3}$/.test(blocker.criterionPath ?? ''))
201
+ e.push('$.blocker.criterionPath: closed criterion path required');
202
+ if (blocker?.evidence?.factRefs && new Set(blocker.evidence.factRefs).size !== blocker.evidence.factRefs.length)
203
+ e.push('$.blocker.evidence.factRefs: unique refs required');
240
204
  if (x.kind === 'question' && blocker && Array.isArray(x.question?.approvalOptions)) {
241
- for (const [i, option] of x.question.approvalOptions.entries()){
205
+ for (const [i, option] of x.question.approvalOptions.entries()) {
242
206
  const result = validateApprovalAgainstBlocker(option?.approval, blocker);
243
- if (!result.ok) e.push(...result.errors.map((error)=>`$.question.approvalOptions[${i}].${error.slice(2)}`));
244
- if (option?.approval?.taskId !== x.taskId) e.push(`$.question.approvalOptions[${i}].approval.taskId: must match event`);
245
- if (option?.approval?.contextRef !== x.contextRef) e.push(`$.question.approvalOptions[${i}].approval.contextRef: must match event`);
207
+ if (!result.ok)
208
+ e.push(...result.errors.map(error => `$.question.approvalOptions[${i}].${error.slice(2)}`));
209
+ if (option?.approval?.taskId !== x.taskId)
210
+ e.push(`$.question.approvalOptions[${i}].approval.taskId: must match event`);
211
+ if (option?.approval?.contextRef !== x.contextRef)
212
+ e.push(`$.question.approvalOptions[${i}].approval.contextRef: must match event`);
246
213
  }
247
214
  }
248
215
  if (kinds.has(String(x.kind)) && x.relaxationApprovalRef) {
249
216
  const ref = x.relaxationApprovalRef;
250
- if (ref.contextRef !== x.contextRef) e.push('$.relaxationApprovalRef.contextRef: must match action');
251
- if (ref.sourceRevision !== x.expectedRevision) e.push('$.relaxationApprovalRef.sourceRevision: must equal action revision');
252
- if (ref.targetActionKind !== x.kind) e.push('$.relaxationApprovalRef.targetActionKind: must match action kind');
253
- if (ref.targetActionId !== x.actionId) e.push('$.relaxationApprovalRef.targetActionId: must match action id');
254
- if (!safeOpaque(ref.nonce) || !isBookingDateTime(ref.expiresAt)) e.push('$.relaxationApprovalRef: invalid expiry or nonce');
217
+ if (ref.contextRef !== x.contextRef)
218
+ e.push('$.relaxationApprovalRef.contextRef: must match action');
219
+ if (ref.sourceRevision !== x.expectedRevision)
220
+ e.push('$.relaxationApprovalRef.sourceRevision: must equal action revision');
221
+ if (ref.targetActionKind !== x.kind)
222
+ e.push('$.relaxationApprovalRef.targetActionKind: must match action kind');
223
+ if (ref.targetActionId !== x.actionId)
224
+ e.push('$.relaxationApprovalRef.targetActionId: must match action id');
225
+ // Expiry and one-time consumption are runtime concerns; validation checks only structure/binding.
226
+ if (!safeOpaque(ref.nonce) || !isBookingDateTime(ref.expiresAt))
227
+ e.push('$.relaxationApprovalRef: invalid expiry or nonce');
255
228
  }
256
- if (x.kind === 'question' && (!Array.isArray(x.question?.approvalOptions) || x.question.approvalOptions.length < 1 || x.question.approvalOptions.length > 2)) e.push('$.question.approvalOptions: one or two options required');
229
+ if (x.kind === 'question' && (!Array.isArray(x.question?.approvalOptions) || x.question.approvalOptions.length < 1 || x.question.approvalOptions.length > 2))
230
+ e.push('$.question.approvalOptions: one or two options required');
257
231
  return e;
258
232
  }
259
- export function validateBookingSurface(value) {
260
- const ok = validate(value);
261
- const e = ok ? [] : errors(validate);
262
- e.push(...cross(value));
263
- return e.length ? {
264
- ok: false,
265
- errors: e
266
- } : {
267
- ok: true
268
- };
269
- }
270
- function fromCanonical(fn, value) {
271
- const ok = fn(value);
272
- const e = ok ? [] : errors(fn);
273
- e.push(...cross(value));
274
- return e.length ? {
275
- ok: false,
276
- errors: e
277
- } : {
278
- ok: true
279
- };
280
- }
281
- export function validateBookingReadAction(value) {
282
- return fromCanonical(validateActionCanonical, value);
283
- }
284
- export function validateCriterionBlocker(value) {
285
- return fromCanonical(validateBlockerCanonical, value);
286
- }
287
- export function validateRelaxationApproval(value) {
288
- return fromCanonical(validateApprovalCanonical, value);
289
- }
290
- export function validateBookingSurfaceEvent(value) {
291
- return fromCanonical(validateEventCanonical, value);
292
- }
233
+ export function validateBookingSurface(value) { const ok = validate(value); const e = ok ? [] : errors(validate); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true }; }
234
+ function fromCanonical(fn, value) { const ok = fn(value); const e = ok ? [] : errors(fn); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true }; }
235
+ export function validateBookingReadAction(value) { return fromCanonical(validateActionCanonical, value); }
236
+ export function validateCriterionBlocker(value) { return fromCanonical(validateBlockerCanonical, value); }
237
+ export function validateRelaxationApproval(value) { return fromCanonical(validateApprovalCanonical, value); }
238
+ export function validateBookingSurfaceEvent(value) { return fromCanonical(validateEventCanonical, value); }
293
239
  export function validateApprovalAgainstBlocker(approval, blocker) {
294
240
  const a = approval;
295
241
  const b = blocker;
296
242
  const blockerResult = validateCriterionBlocker(b);
297
- if (!blockerResult.ok) return blockerResult;
243
+ if (!blockerResult.ok)
244
+ return blockerResult;
298
245
  const approvalResult = validateRelaxationApproval(a);
299
- if (!approvalResult.ok) return approvalResult;
300
- const fields = [
301
- 'blockerId',
302
- 'sourceActionId',
303
- 'sourceReceiptDigest',
304
- 'scope',
305
- 'code',
306
- 'criterionPath',
307
- 'valueDigest'
308
- ];
309
- const mismatches = fields.filter((field)=>a[field] !== b?.[field]);
310
- return mismatches.length ? {
311
- ok: false,
312
- errors: mismatches.map((field)=>`$.${field}: approval does not match blocker`)
313
- } : {
314
- ok: true
315
- };
246
+ if (!approvalResult.ok)
247
+ return approvalResult;
248
+ const fields = ['blockerId', 'sourceActionId', 'sourceReceiptDigest', 'scope', 'code', 'criterionPath', 'valueDigest'];
249
+ const mismatches = fields.filter(field => a[field] !== b?.[field]);
250
+ return mismatches.length ? { ok: false, errors: mismatches.map(field => `$.${field}: approval does not match blocker`) } : { ok: true };
316
251
  }
317
-
318
-
319
- //# sourceURL=ts/src/booking-surface/validation.ts
@@ -1,34 +1,41 @@
1
+ /**
2
+ * GoTry 桥接工具集(纯 Node 版,无 Python 依赖):
3
+ * - ensureStateDir: 状态目录创建
4
+ * - recordLatency: 延迟日志追加
5
+ * - readJson / writeJson: JSON 文件安全读写
6
+ *
7
+ * 历史:曾有 callFeasibilityEngine 用 spawn(.venv/bin/python) 起 Python CLI,
8
+ * 作为 TS 求解的回退路径。D-7 切轨后 unified solveChoiceSegment 是唯一求解入口,
9
+ * Python oracle 路径于 v0.0.1-rc.2 移除——npm 一键分发不再需要 Python 运行时依赖。
10
+ */
1
11
  import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
2
12
  import { join } from 'node:path';
13
+ /** 状态目录:动机画像 / wish pool 的落盘位置(红线 6:用户数据可见、可编辑、可删除) */
3
14
  export async function ensureStateDir(root) {
4
15
  const dir = join(root, 'gotry-state');
5
- await mkdir(dir, {
6
- recursive: true
7
- });
16
+ await mkdir(dir, { recursive: true });
8
17
  return dir;
9
18
  }
19
+ /** JSON 安全读取:文件不存在返回 fallback,解析失败抛错 */
10
20
  export async function readJson(path, fallback) {
11
21
  try {
12
22
  const text = await readFile(path, 'utf-8');
13
23
  return JSON.parse(text);
14
- } catch (e) {
15
- if (e.code === 'ENOENT') return fallback;
24
+ }
25
+ catch (e) {
26
+ if (e.code === 'ENOENT')
27
+ return fallback;
16
28
  throw e;
17
29
  }
18
30
  }
31
+ /** JSON 原子写入 */
19
32
  export async function writeJson(path, value) {
20
33
  const tmp = `${path}.tmp`;
21
34
  await writeFile(tmp, JSON.stringify(value, null, 2), 'utf-8');
22
35
  const { rename } = await import('node:fs/promises');
23
36
  await rename(tmp, path);
24
37
  }
38
+ /** 桥接延迟日志(追加式;T5 成本工程的度量数据源之一) */
25
39
  export async function recordLatency(logPath, latencyMs, kind) {
26
- await appendFile(logPath, JSON.stringify({
27
- ts: new Date().toISOString(),
28
- kind,
29
- latencyMs
30
- }) + '\n');
40
+ await appendFile(logPath, JSON.stringify({ ts: new Date().toISOString(), kind, latencyMs }) + '\n');
31
41
  }
32
-
33
-
34
- //# sourceURL=ts/src/bridge.ts