@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28

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 (533) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1788 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +161 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +553 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +676 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/provider-detail-sanitize.js +64 -0
  41. package/dist/capabilities/resilience.js +191 -0
  42. package/dist/capabilities/route-provider-conformance.js +341 -0
  43. package/dist/capabilities/session/action-cache.js +50 -44
  44. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  45. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  46. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  47. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  48. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  49. package/dist/capabilities/session/benchmark.js +189 -0
  50. package/dist/capabilities/session/bridge-ledger.js +224 -0
  51. package/dist/capabilities/session/extension-bridge.js +636 -0
  52. package/dist/capabilities/session/extension-channel.js +109 -0
  53. package/dist/capabilities/session/extension-distribution.js +230 -0
  54. package/dist/capabilities/session/extract.js +31 -21
  55. package/dist/capabilities/session/golden-score.js +114 -0
  56. package/dist/capabilities/session/health-watch.js +215 -0
  57. package/dist/capabilities/session/read-guard.js +75 -24
  58. package/dist/capabilities/session/static-flight-golden.js +150 -0
  59. package/dist/capabilities/session/transport.js +97 -83
  60. package/dist/capabilities/session/wizard.js +117 -0
  61. package/dist/capabilities/session-consent.js +178 -0
  62. package/dist/capabilities/session-login.js +170 -0
  63. package/dist/capabilities/session-search.js +635 -44
  64. package/dist/capabilities/spawn-bounded.js +203 -0
  65. package/dist/capabilities/sponsor-plugin.js +341 -0
  66. package/dist/capabilities/visa-policy.js +140 -0
  67. package/dist/capabilities/weather.js +243 -138
  68. package/dist/data/session-golden-20.json +25 -0
  69. package/dist/data/sf-golden-manifest.json +103 -0
  70. package/dist/data/sf-static-routes.json +91 -0
  71. package/dist/scripts/action-cache-tests.js +24 -59
  72. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  73. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  74. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  75. package/dist/scripts/agent-reach-error-tests.js +354 -0
  76. package/dist/scripts/agent-reach-tests.js +26 -18
  77. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  78. package/dist/scripts/anything-path-measure-tests.js +133 -0
  79. package/dist/scripts/anything-path-measure.js +150 -0
  80. package/dist/scripts/anything-tests.js +250 -60
  81. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  82. package/dist/scripts/artifacts-capability-tests.js +727 -0
  83. package/dist/scripts/async-collect.js +50 -11
  84. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  85. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  86. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  87. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  88. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  89. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  90. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  91. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  92. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  93. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  94. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  95. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
  96. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  97. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  98. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  99. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  100. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  101. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  102. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  103. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  104. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  105. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  106. package/dist/scripts/booking-executor-tests.js +80 -0
  107. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  108. package/dist/scripts/booking-saga-tests.js +125 -0
  109. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  110. package/dist/scripts/bootstrap-tests.js +1549 -0
  111. package/dist/scripts/bridge-events-tests.js +239 -0
  112. package/dist/scripts/bridge-ledger-tests.js +428 -0
  113. package/dist/scripts/build-changelog.js +238 -0
  114. package/dist/scripts/build-metrics-report.js +331 -0
  115. package/dist/scripts/changelog-tests.js +118 -0
  116. package/dist/scripts/channel-probe-tests.js +103 -0
  117. package/dist/scripts/channel-probe.js +169 -0
  118. package/dist/scripts/channel-registry-tests.js +208 -0
  119. package/dist/scripts/city-scenario-tier-tests.js +234 -0
  120. package/dist/scripts/companion-tests.js +25 -70
  121. package/dist/scripts/dida-cookie-probe.js +87 -0
  122. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  123. package/dist/scripts/dida-sw-probe.js +114 -0
  124. package/dist/scripts/diff-test.js +17 -12
  125. package/dist/scripts/doctor-tests.js +600 -0
  126. package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
  127. package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
  128. package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
  129. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  130. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  131. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  132. package/dist/scripts/dsh-llm-tests.js +437 -0
  133. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  134. package/dist/scripts/dsh-runtime-closure.js +162 -0
  135. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  136. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  137. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  138. package/dist/scripts/effect-tests.js +539 -0
  139. package/dist/scripts/engine-run.js +1 -3
  140. package/dist/scripts/engine-tests.js +16 -10
  141. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  142. package/dist/scripts/evaluation-contract-tests.js +268 -0
  143. package/dist/scripts/extension-distribution-cli.js +39 -0
  144. package/dist/scripts/extension-distribution-tests.js +443 -0
  145. package/dist/scripts/extension-tests.js +988 -0
  146. package/dist/scripts/external-event-tests.js +350 -0
  147. package/dist/scripts/fact-gate-tests.js +869 -0
  148. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  149. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  150. package/dist/scripts/flight-malformed-tests.js +159 -0
  151. package/dist/scripts/flyai-setup-tests.js +228 -0
  152. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  153. package/dist/scripts/flyai-tests.js +692 -0
  154. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  155. package/dist/scripts/fx-contract-tests.js +201 -0
  156. package/dist/scripts/g5-guard-tests.js +150 -0
  157. package/dist/scripts/g5-guard.js +193 -0
  158. package/dist/scripts/gen-lunar-anchors.js +106 -0
  159. package/dist/scripts/geo-atlas-tests.js +283 -0
  160. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  161. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  162. package/dist/scripts/gotry-try-demo.js +49 -0
  163. package/dist/scripts/ground-transfer-tests.js +956 -0
  164. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  165. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  166. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  167. package/dist/scripts/hbcli-tests.js +121 -69
  168. package/dist/scripts/health-watch-cli.js +73 -0
  169. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  170. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  171. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  172. package/dist/scripts/i18n-tests.js +41 -0
  173. package/dist/scripts/incident-tests.js +281 -87
  174. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  175. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  176. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  177. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  178. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  179. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  180. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  181. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  182. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  183. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  184. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  185. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  186. package/dist/scripts/itinerary-deck-tests.js +435 -0
  187. package/dist/scripts/itinerary-html-tests.js +426 -0
  188. package/dist/scripts/journey-tests.js +22 -27
  189. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  190. package/dist/scripts/kernel-manifest-gate.js +126 -0
  191. package/dist/scripts/kernel-manifest-tests.js +212 -0
  192. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  193. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  194. package/dist/scripts/kernel-manifest.js +369 -0
  195. package/dist/scripts/lavish-local-tests.js +1226 -0
  196. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  197. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  198. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  199. package/dist/scripts/ledger-tests.js +382 -246
  200. package/dist/scripts/ledger-workflow-crash.js +10 -20
  201. package/dist/scripts/m3-cohort-tests.js +714 -0
  202. package/dist/scripts/m3-cohort.js +310 -0
  203. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  204. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  205. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  206. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  207. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  208. package/dist/scripts/memory-capture-tests.js +242 -61
  209. package/dist/scripts/memory-decay-tests.js +18 -52
  210. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  211. package/dist/scripts/memory-lifecycle.js +196 -0
  212. package/dist/scripts/memory-metrics.js +24 -12
  213. package/dist/scripts/memory-value-report.js +631 -0
  214. package/dist/scripts/memory-value-tests.js +328 -0
  215. package/dist/scripts/metrics-report-tests.js +151 -0
  216. package/dist/scripts/model-override-e2e.js +137 -0
  217. package/dist/scripts/nightly-evidence-tests.js +203 -0
  218. package/dist/scripts/nightly-evidence.js +303 -0
  219. package/dist/scripts/nudge-digest.js +39 -28
  220. package/dist/scripts/observability-tests.js +381 -0
  221. package/dist/scripts/onboarding-tests.js +161 -0
  222. package/dist/scripts/opensky-check.js +18 -13
  223. package/dist/scripts/opensky-tests.js +28 -28
  224. package/dist/scripts/outcome-projection-tests.js +427 -0
  225. package/dist/scripts/persona-sim-tests.js +603 -0
  226. package/dist/scripts/persona-sim.js +1081 -0
  227. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  228. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  229. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  230. package/dist/scripts/price-drift-tests.js +340 -0
  231. package/dist/scripts/price-drift-watch.js +447 -0
  232. package/dist/scripts/probe-poi-tests.js +53 -45
  233. package/dist/scripts/product-metrics.js +483 -0
  234. package/dist/scripts/publish-preverify.js +110 -0
  235. package/dist/scripts/realtime-pricing-tests.js +76 -0
  236. package/dist/scripts/recall-tests.js +513 -0
  237. package/dist/scripts/replay-async.js +22 -18
  238. package/dist/scripts/replay-real.js +18 -18
  239. package/dist/scripts/replay.js +54 -82
  240. package/dist/scripts/route-provider-conformance-tests.js +391 -0
  241. package/dist/scripts/session-attach-diagnose.js +20 -20
  242. package/dist/scripts/session-attach-poc.js +34 -37
  243. package/dist/scripts/session-benchmark.js +180 -0
  244. package/dist/scripts/session-dida-live-e2e.js +199 -0
  245. package/dist/scripts/session-extract-tests.js +14 -35
  246. package/dist/scripts/session-link-tests.js +478 -0
  247. package/dist/scripts/session-login.js +27 -46
  248. package/dist/scripts/session-tests.js +796 -96
  249. package/dist/scripts/session-zone-crash.js +49 -0
  250. package/dist/scripts/session-zone-ledger-tests.js +432 -0
  251. package/dist/scripts/session-zone-observation-tests.js +492 -0
  252. package/dist/scripts/session-zone-observe.js +135 -0
  253. package/dist/scripts/session-zone-wiring-tests.js +566 -0
  254. package/dist/scripts/session-zones-tests.js +374 -0
  255. package/dist/scripts/sf-live-benchmark.js +367 -0
  256. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  257. package/dist/scripts/sf-live-cli-tests.js +92 -0
  258. package/dist/scripts/sf-live-options.js +46 -0
  259. package/dist/scripts/sf-manifest-tests.js +30 -0
  260. package/dist/scripts/sf-manifest.js +33 -0
  261. package/dist/scripts/sf-soft-score-tests.js +67 -0
  262. package/dist/scripts/sf-summary-tests.js +324 -0
  263. package/dist/scripts/sf-summary.js +525 -0
  264. package/dist/scripts/share-tests.js +432 -0
  265. package/dist/scripts/skeleton-check.js +16 -14
  266. package/dist/scripts/skeleton-integration-test.js +28 -11
  267. package/dist/scripts/skills-contract-tests.js +51 -46
  268. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  269. package/dist/scripts/smoke.js +690 -246
  270. package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
  271. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  272. package/dist/scripts/state-cli-tests.js +226 -101
  273. package/dist/scripts/state-cli.js +565 -198
  274. package/dist/scripts/state-repair-cli.js +66 -0
  275. package/dist/scripts/state-repair-tests.js +140 -0
  276. package/dist/scripts/static-golden-tests.js +165 -0
  277. package/dist/scripts/time-eval-tests.js +212 -192
  278. package/dist/scripts/travel-timeline-tests.js +29 -93
  279. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  280. package/dist/scripts/turn-handoff-collect.js +161 -0
  281. package/dist/scripts/turn-policy-tests.js +55 -0
  282. package/dist/scripts/typed-contract-canary.js +176 -0
  283. package/dist/scripts/tz-resolver-sanity.js +50 -0
  284. package/dist/scripts/tz-resolver-tests.js +584 -0
  285. package/dist/scripts/unified-tests.js +60 -20
  286. package/dist/scripts/visa-policy-tests.js +62 -0
  287. package/dist/scripts/weather-tests.js +276 -41
  288. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  289. package/dist/scripts/write-gate-tests.js +522 -0
  290. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  291. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  292. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  293. package/dist/scripts/z3-race-tests.js +46 -0
  294. package/dist/src/artifact-gate.js +829 -0
  295. package/dist/src/backend/kernel.js +91 -0
  296. package/dist/src/backend/modules/booking-copilot.js +92 -0
  297. package/dist/src/backend/modules/booking-executor.js +124 -0
  298. package/dist/src/backend/modules/session-search.js +551 -0
  299. package/dist/src/benchmark-agent-conformance.js +578 -0
  300. package/dist/src/benchmark-environment-bridge.js +515 -0
  301. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  302. package/dist/src/benchmark-tool-isolation.js +151 -0
  303. package/dist/src/bookable-facts.js +533 -0
  304. package/dist/src/booking-saga.js +97 -0
  305. package/dist/src/booking-surface/availability-policy.js +697 -0
  306. package/dist/src/booking-surface/booking-intent.js +43 -0
  307. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  308. package/dist/src/booking-surface/canonical-schema.js +116 -0
  309. package/dist/src/booking-surface/contracts.js +75 -0
  310. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  311. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  312. package/dist/src/booking-surface/dsh-worker.js +98 -0
  313. package/dist/src/booking-surface/error-codes.js +231 -0
  314. package/dist/src/booking-surface/index.js +9 -0
  315. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  316. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  317. package/dist/src/booking-surface/profile.js +24 -0
  318. package/dist/src/booking-surface/recovery-chain.js +367 -0
  319. package/dist/src/booking-surface/runtime.js +1968 -0
  320. package/dist/src/booking-surface/server.js +674 -0
  321. package/dist/src/booking-surface/startup.js +168 -0
  322. package/dist/src/booking-surface/validation.js +261 -0
  323. package/dist/src/bridge.js +20 -13
  324. package/dist/src/city-scenario-tier.js +378 -0
  325. package/dist/src/companions.js +48 -74
  326. package/dist/src/contracts.js +21 -43
  327. package/dist/src/dsh-llm.js +171 -115
  328. package/dist/src/engine.js +276 -193
  329. package/dist/src/evaluation-cadence.js +97 -0
  330. package/dist/src/evaluation-contracts.js +283 -0
  331. package/dist/src/flight-pack-adapter.js +54 -0
  332. package/dist/src/flight-pack-contract.js +2 -0
  333. package/dist/src/flyai-setup-tool.js +70 -0
  334. package/dist/src/gotry-backend.js +125 -0
  335. package/dist/src/hotel-date-gate.js +118 -0
  336. package/dist/src/i18n.js +88 -0
  337. package/dist/src/index.js +2385 -881
  338. package/dist/src/itinerary-deck.js +139 -0
  339. package/dist/src/itinerary-doc-shared.js +661 -0
  340. package/dist/src/itinerary-html.js +131 -0
  341. package/dist/src/journey.js +155 -108
  342. package/dist/src/lavish-tools.js +400 -0
  343. package/dist/src/ledger-repair-apply.js +538 -0
  344. package/dist/src/ledger-repair-plan.js +463 -0
  345. package/dist/src/loop.js +331 -161
  346. package/dist/src/m3-cohort.js +1577 -0
  347. package/dist/src/memory-capture.js +180 -32
  348. package/dist/src/memory-decay.js +30 -14
  349. package/dist/src/memory-lifecycle.js +1310 -0
  350. package/dist/src/memory-utility.js +27 -40
  351. package/dist/src/mock-llm.js +36 -61
  352. package/dist/src/model.js +81 -36
  353. package/dist/src/outcome-projection.js +746 -0
  354. package/dist/src/realtime-pricing.js +103 -0
  355. package/dist/src/recall/card.js +74 -0
  356. package/dist/src/recall/evaluator.js +140 -0
  357. package/dist/src/recall/integration.js +17 -0
  358. package/dist/src/recall/tick.js +166 -0
  359. package/dist/src/session-link/plan-it-action.js +48 -0
  360. package/dist/src/session-link/session-link.js +248 -0
  361. package/dist/src/session-zone-ledger.js +312 -0
  362. package/dist/src/session-zone-observation.js +354 -0
  363. package/dist/src/session-zone-wiring.js +658 -0
  364. package/dist/src/session-zones.js +510 -0
  365. package/dist/src/share/adapters.js +51 -0
  366. package/dist/src/share/share-consent.js +76 -0
  367. package/dist/src/share/share-deck.js +58 -0
  368. package/dist/src/share/share-token.js +146 -0
  369. package/dist/src/slot-spec.js +84 -106
  370. package/dist/src/state-ledger.js +696 -524
  371. package/dist/src/state-repair.js +299 -0
  372. package/dist/src/subagent-job-id-guard.js +58 -0
  373. package/dist/src/time-anchor.js +231 -33
  374. package/dist/src/tool-packet.js +20 -8
  375. package/dist/src/travel-slots.js +58 -35
  376. package/dist/src/travel-timeline.js +44 -63
  377. package/dist/src/turn-deadline.js +287 -0
  378. package/dist/src/turn-policy.js +118 -0
  379. package/dist/src/tz-resolver.js +143 -0
  380. package/dist/src/unified.js +600 -360
  381. package/dist/src/wish-pool.js +42 -12
  382. package/dist/src/write-gate.js +495 -0
  383. package/dist/src/z3-shared.js +284 -0
  384. package/extension/README.md +21 -0
  385. package/extension/README.zh-CN.md +21 -0
  386. package/extension/background.js +463 -0
  387. package/extension/content-bridge.js +270 -0
  388. package/extension/content-main.js +111 -0
  389. package/extension/manifest.json +60 -0
  390. package/package.json +407 -14
  391. package/schemas/booking.intent.schema.json +193 -0
  392. package/schemas/booking.surface.schema.json +2622 -0
  393. package/ts/capabilities/agent-reach-deep.ts +114 -0
  394. package/ts/capabilities/agent-reach.ts +198 -0
  395. package/ts/capabilities/anything.ts +282 -0
  396. package/ts/capabilities/artifacts.ts +428 -0
  397. package/ts/capabilities/fact-log.ts +54 -0
  398. package/ts/capabilities/flyai-config.ts +388 -0
  399. package/ts/capabilities/flyai.ts +1274 -0
  400. package/ts/capabilities/hbcli.ts +253 -71
  401. package/ts/capabilities/incident-log.ts +88 -59
  402. package/ts/capabilities/itinerary-artifact.ts +214 -0
  403. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  404. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  405. package/ts/capabilities/opensky.ts +121 -0
  406. package/ts/capabilities/session/action-cache.ts +148 -0
  407. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  408. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  409. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  410. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  411. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  412. package/ts/capabilities/session/benchmark.ts +276 -0
  413. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  414. package/ts/capabilities/session/extension-bridge.ts +794 -0
  415. package/ts/capabilities/session/extension-channel.ts +144 -0
  416. package/ts/capabilities/session/extension-distribution.ts +266 -0
  417. package/ts/capabilities/session/extract.ts +54 -0
  418. package/ts/capabilities/session/golden-score.ts +139 -0
  419. package/ts/capabilities/session/health-watch.ts +264 -0
  420. package/ts/capabilities/session/read-guard.ts +141 -0
  421. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  422. package/ts/capabilities/session/transport.ts +146 -0
  423. package/ts/capabilities/session/wizard.ts +140 -0
  424. package/ts/capabilities/session-consent.ts +211 -0
  425. package/ts/capabilities/session-login.ts +208 -0
  426. package/ts/capabilities/session-search.ts +893 -0
  427. package/ts/capabilities/weather.ts +330 -0
  428. package/ts/dsh-runtime/vendor/README.md +68 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  440. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  441. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  442. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  443. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  444. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  445. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  446. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  447. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  448. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  449. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  450. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  451. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  452. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  453. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  454. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  455. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  456. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  457. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  458. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  459. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  460. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  461. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  462. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  463. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  464. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  465. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  466. package/ts/package.json +293 -3
  467. package/ts/scripts/async-collect.ts +91 -0
  468. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  469. package/ts/scripts/skeleton-check.ts +1 -1
  470. package/ts/scripts/skeleton-integration-test.ts +23 -1
  471. package/ts/scripts/state-cli.ts +586 -0
  472. package/ts/scripts/turn-handoff-collect.ts +178 -0
  473. package/ts/src/artifact-gate.ts +991 -0
  474. package/ts/src/benchmark-agent-conformance.ts +619 -0
  475. package/ts/src/benchmark-environment-bridge.ts +538 -0
  476. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  477. package/ts/src/benchmark-tool-isolation.ts +166 -0
  478. package/ts/src/bookable-facts.ts +837 -0
  479. package/ts/src/booking-saga.ts +133 -0
  480. package/ts/src/booking-surface/availability-policy.ts +522 -0
  481. package/ts/src/booking-surface/booking-intent.ts +86 -0
  482. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  483. package/ts/src/booking-surface/canonical-schema.js +116 -0
  484. package/ts/src/booking-surface/contracts.ts +475 -0
  485. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  486. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  487. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  488. package/ts/src/booking-surface/error-codes.ts +241 -0
  489. package/ts/src/booking-surface/index.ts +9 -0
  490. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  491. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  492. package/ts/src/booking-surface/profile.ts +42 -0
  493. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  494. package/ts/src/booking-surface/runtime.ts +1793 -0
  495. package/ts/src/booking-surface/server.ts +733 -0
  496. package/ts/src/booking-surface/startup.ts +201 -0
  497. package/ts/src/booking-surface/validation.ts +217 -0
  498. package/ts/src/companions.ts +113 -0
  499. package/ts/src/contracts.ts +17 -3
  500. package/ts/src/dsh-llm.ts +152 -17
  501. package/ts/src/flyai-setup-tool.ts +77 -0
  502. package/ts/src/index.ts +2123 -200
  503. package/ts/src/itinerary-deck.ts +189 -0
  504. package/ts/src/itinerary-doc-shared.ts +741 -0
  505. package/ts/src/itinerary-html.ts +171 -0
  506. package/ts/src/loop.ts +234 -26
  507. package/ts/src/memory-capture.ts +283 -0
  508. package/ts/src/memory-decay.ts +55 -0
  509. package/ts/src/memory-utility.ts +70 -0
  510. package/ts/src/mock-llm.ts +4 -8
  511. package/ts/src/model.ts +88 -2
  512. package/ts/src/recall/card.ts +106 -0
  513. package/ts/src/recall/evaluator.ts +170 -0
  514. package/ts/src/recall/integration.ts +35 -0
  515. package/ts/src/recall/tick.ts +213 -0
  516. package/ts/src/session-link/plan-it-action.ts +78 -0
  517. package/ts/src/session-link/session-link.ts +275 -0
  518. package/ts/src/share/adapters.ts +93 -0
  519. package/ts/src/share/share-consent.ts +105 -0
  520. package/ts/src/share/share-deck.ts +85 -0
  521. package/ts/src/share/share-token.ts +166 -0
  522. package/ts/src/slot-spec.ts +172 -0
  523. package/ts/src/state-ledger.ts +1166 -0
  524. package/ts/src/subagent-job-id-guard.ts +72 -0
  525. package/ts/src/time-anchor.ts +338 -0
  526. package/ts/src/tool-packet.ts +32 -0
  527. package/ts/src/travel-slots.ts +219 -0
  528. package/ts/src/travel-timeline.ts +83 -0
  529. package/ts/src/turn-deadline.ts +366 -0
  530. package/ts/src/turn-policy.ts +154 -0
  531. package/ts/src/unified.ts +444 -105
  532. package/ts/src/wish-pool.ts +79 -0
  533. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,244 @@
1
+ /**
2
+ * spawn-bounded 取消/进程树回收 E2E(issue #519;真实进程,非 mock):
3
+ * 1. pre-aborted signal → 零 spawn;
4
+ * 2. 启动真父子(父忽略 SIGTERM、持续 spawn 孙)→ abort →
5
+ * 父与孙均被杀,有界等待,结果语义 aborted;
6
+ * 3. 超时 → SIGKILL 进程树;
7
+ * 4. abort 不触发候选重试(直接 callHbcliJson 双候选场景验证)。
8
+ * 全程临时目录 fixture,结束前 kill -0 复核并兜底清理同根 pid。
9
+ * 运行: cd ts && npx tsx scripts/spawn-bounded-abort-tests.ts
10
+ */
11
+ import assert from 'node:assert/strict';
12
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { spawnBounded } from '../capabilities/spawn-bounded.js';
16
+ import { callHbcliJson, searchHotels } from '../capabilities/hbcli.js';
17
+ import { anythingSearch } from '../capabilities/anything.js';
18
+ // issue #617:本套件要真起 fake hbcli(进程树回收证明),而 `GOTRY_HBCLI_LIVE=0 ./scripts/run-all-tests.sh`
19
+ // 会把 anythingSearch 的离线开关带进来——此处显式开启,不让外部环境改变进程回收断言的对象。
20
+ delete process.env['GOTRY_HBCLI_LIVE'];
21
+ const root = mkdtempSync(join(tmpdir(), 'gotry-519-abort-'));
22
+ const ownedPids = new Set();
23
+ let activeWork;
24
+ let activeAbort;
25
+ const waitForFile = async (path, label, boundMs = 5_000) => {
26
+ let settled = false;
27
+ let outcome;
28
+ activeWork?.then(value => { settled = true; outcome = value; }, error => { settled = true; outcome = String(error); });
29
+ const deadline = Date.now() + boundMs;
30
+ while (!existsSync(path) && !settled && Date.now() < deadline) {
31
+ await new Promise(r => setTimeout(r, 10));
32
+ }
33
+ assert.ok(existsSync(path), `${label} ready handshake failed within ${boundMs}ms; settled=${settled}; outcome=${JSON.stringify(outcome)}`);
34
+ };
35
+ const pidAlive = (pid) => {
36
+ try {
37
+ process.kill(pid, 0);
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ };
44
+ const waitForPidDead = async (pid, boundMs = 1_500) => {
45
+ const deadline = Date.now() + boundMs;
46
+ while (pidAlive(pid) && Date.now() < deadline) {
47
+ await new Promise(r => setTimeout(r, 10));
48
+ }
49
+ assert.equal(pidAlive(pid), false, `owned pid ${pid} survived cleanup bound`);
50
+ };
51
+ const cleanupOwnedProcesses = () => {
52
+ for (const pid of ownedPids) {
53
+ try {
54
+ process.kill(pid, 'SIGKILL');
55
+ }
56
+ catch { /* already exited */ }
57
+ }
58
+ };
59
+ try {
60
+ const marker = join(root, 'started');
61
+ const preabortBin = join(root, 'preabort.js');
62
+ writeFileSync(preabortBin, `#!${process.execPath}
63
+ const fs=require('node:fs');fs.writeFileSync(${JSON.stringify(marker)},'started');console.log('{}');`, { mode: 0o700 });
64
+ // 1) pre-aborted:零 spawn
65
+ const pre = new AbortController();
66
+ pre.abort();
67
+ const preResult = await spawnBounded(preabortBin, [], { env: process.env, timeoutMs: 1000, signal: pre.signal });
68
+ assert.equal(preResult.aborted, true, JSON.stringify(preResult));
69
+ assert.equal(preResult.groupReaped, true);
70
+ assert.equal(existsSync(marker), false, 'pre-aborted signal must produce zero spawn');
71
+ // 经 callHbcliJson(带 fallback 候选语义)同样零 spawn、不触发候选
72
+ const preViaApi = await callHbcliJson([], { hbcliBin: preabortBin, timeoutMs: 1000, signal: pre.signal });
73
+ assert.equal(preViaApi.via, 'hbcli-error');
74
+ assert.equal(/abort/.test(preViaApi.error ?? ''), true, preViaApi.error);
75
+ assert.equal(existsSync(marker), false, 'callHbcliJson pre-aborted must not spawn either');
76
+ const preViaSearch = await searchHotels({ destination: '普吉岛' }, { hbcliBin: preabortBin, fallbackPath: join(root, 'missing-fallback.json'), signal: pre.signal });
77
+ assert.match(preViaSearch.summary, /检索已取消/);
78
+ assert.doesNotMatch(preViaSearch.summary, /静态包/);
79
+ // 2) 真父子进程树:父捕获 SIGTERM 不退出、孙独立常驻
80
+ const childBin = join(root, 'descendant.js');
81
+ const parentBin = join(root, 'parent.js');
82
+ const pidsFile = join(root, 'pids.json');
83
+ const readyFile = join(root, 'ready');
84
+ writeFileSync(childBin, `setInterval(()=>{},1000)\n`);
85
+ writeFileSync(parentBin, `#!${process.execPath}
86
+ const {spawn}=require('node:child_process');const fs=require('node:fs');
87
+ process.on('SIGTERM',()=>{});
88
+ const c=spawn(process.execPath,[${JSON.stringify(childBin)}]);
89
+ fs.writeFileSync(${JSON.stringify(pidsFile)},JSON.stringify({parent:process.pid,child:c.pid}));
90
+ fs.writeFileSync(${JSON.stringify(readyFile)},'ready');
91
+ setInterval(()=>{},1000);`, { mode: 0o700 });
92
+ const abort = new AbortController();
93
+ activeAbort = abort;
94
+ const work = spawnBounded(parentBin, [], { env: process.env, timeoutMs: 10_000, signal: abort.signal });
95
+ activeWork = work;
96
+ await waitForFile(readyFile, 'process tree');
97
+ const ids = JSON.parse(readFileSync(pidsFile, 'utf8'));
98
+ ownedPids.add(ids.parent);
99
+ ownedPids.add(ids.child);
100
+ const abortStarted = Date.now();
101
+ abort.abort();
102
+ const result = await work;
103
+ activeWork = undefined;
104
+ activeAbort = undefined;
105
+ const recycleElapsed = Date.now() - abortStarted;
106
+ assert.equal(result.aborted, true, JSON.stringify(result));
107
+ assert.equal(result.timedOut, false);
108
+ assert.equal(result.groupReaped, true, JSON.stringify(result));
109
+ // 有界回收:TERM_GRACE(500)+ 观测窗,父忽略 SIGTERM 由 SIGKILL 兜底
110
+ assert.ok(recycleElapsed < 3_000, `abort bounded wait exceeded: ${recycleElapsed}ms`);
111
+ assert.equal(pidAlive(ids.parent), false, 'parent survived abort');
112
+ assert.equal(pidAlive(ids.child), false, 'descendant survived abort — kill of parent pid is not tree cleanup');
113
+ // 2a) 脱离进程组的自有子进程继续持有 stdout/stderr:close 不应无限等待。
114
+ // child 设 20s 自退只用于让旧实现稳定红灯;finally 仍按已知 PID 立即清理并读回。
115
+ const escapedBin = join(root, 'escaped-child.js');
116
+ const escapedParentBin = join(root, 'escaped-parent.js');
117
+ const escapedPidsFile = join(root, 'escaped-pids.json');
118
+ const escapedReadyFile = join(root, 'escaped-ready');
119
+ writeFileSync(escapedBin, `setInterval(()=>{},1000);setTimeout(()=>process.exit(0),20000);\n`);
120
+ writeFileSync(escapedParentBin, `#!${process.execPath}
121
+ const {spawn}=require('node:child_process');const fs=require('node:fs');
122
+ const c=spawn(process.execPath,[${JSON.stringify(escapedBin)}],{detached:true,stdio:['ignore','inherit','inherit']});
123
+ c.unref();
124
+ fs.writeFileSync(${JSON.stringify(escapedPidsFile)},JSON.stringify({child:c.pid}));
125
+ fs.writeFileSync(${JSON.stringify(escapedReadyFile)},'ready');
126
+ process.on('SIGTERM',()=>process.exit(0));
127
+ setInterval(()=>{},1000);`, { mode: 0o700 });
128
+ const escapedAbort = new AbortController();
129
+ activeAbort = escapedAbort;
130
+ // Startup has its own 5s readiness bound. Schedule abort 1s before the ordinary
131
+ // 10s timeout so both timers still overlap the 2.5s drain window, even on a cold host.
132
+ const escapedSpawnStarted = Date.now();
133
+ const escapedWork = spawnBounded(escapedParentBin, [], { env: process.env, timeoutMs: 10_000, signal: escapedAbort.signal });
134
+ activeWork = escapedWork;
135
+ await waitForFile(escapedReadyFile, 'escaped process');
136
+ const escapedIds = JSON.parse(readFileSync(escapedPidsFile, 'utf8'));
137
+ ownedPids.add(escapedIds.child);
138
+ // Abort precedes the ordinary timeout; that later timer must not change the cause or extend cleanup.
139
+ await new Promise(r => setTimeout(r, Math.max(0, escapedSpawnStarted + 9_000 - Date.now())));
140
+ assert.ok(Date.now() - escapedSpawnStarted < 10_000, 'fixture must abort before the ordinary timeout');
141
+ const escapedStarted = Date.now();
142
+ escapedAbort.abort();
143
+ const escapedResult = await escapedWork;
144
+ activeWork = undefined;
145
+ activeAbort = undefined;
146
+ const escapedElapsed = Date.now() - escapedStarted;
147
+ assert.ok(escapedElapsed < 3_000, `escaped-pipe settlement exceeded bound: ${escapedElapsed}ms`);
148
+ assert.equal(escapedResult.aborted, true, JSON.stringify(escapedResult));
149
+ assert.equal(escapedResult.groupReaped, false, JSON.stringify(escapedResult));
150
+ assert.match(escapedResult.error ?? '', /cleanup incomplete/, JSON.stringify(escapedResult));
151
+ assert.equal(pidAlive(escapedIds.child), true, 'escaped child should remain for explicit finally cleanup');
152
+ try {
153
+ process.kill(escapedIds.child, 'SIGKILL');
154
+ await waitForPidDead(escapedIds.child);
155
+ }
156
+ finally {
157
+ ownedPids.delete(escapedIds.child);
158
+ }
159
+ // 2a-adapter) hbcli/Anything 必须把同一 cleanup 不完整状态透传到 error 面。
160
+ const adapterCleanupProbe = async (label, invoke) => {
161
+ rmSync(escapedPidsFile, { force: true });
162
+ rmSync(escapedReadyFile, { force: true });
163
+ const controller = new AbortController();
164
+ activeAbort = controller;
165
+ const work = invoke(controller.signal);
166
+ activeWork = work;
167
+ let pid;
168
+ try {
169
+ await waitForFile(escapedReadyFile, `${label} escaped process`);
170
+ pid = JSON.parse(readFileSync(escapedPidsFile, 'utf8')).child;
171
+ ownedPids.add(pid);
172
+ controller.abort();
173
+ const result = await work;
174
+ activeWork = undefined;
175
+ activeAbort = undefined;
176
+ assert.match(result.error ?? '', /process group cleanup incomplete/, `${label} hid cleanup failure`);
177
+ }
178
+ finally {
179
+ controller.abort();
180
+ if (pid !== undefined) {
181
+ try {
182
+ process.kill(pid, 'SIGKILL');
183
+ }
184
+ catch { /* already exited */ }
185
+ await waitForPidDead(pid);
186
+ ownedPids.delete(pid);
187
+ }
188
+ }
189
+ };
190
+ await adapterCleanupProbe('hbcli', signal => callHbcliJson([], { hbcliBin: escapedParentBin, timeoutMs: 10_000, signal }));
191
+ await adapterCleanupProbe('anything', signal => anythingSearch({ keyword: 'x', hbcliBin: escapedParentBin, timeoutMs: 10_000, signal }));
192
+ // 2b) abort 后不走已知安装位候选:首个缺失后启动 ~/.local/bin,第三候选永不执行
193
+ const home = join(root, 'fake-home');
194
+ const secondMarker = join(home, 'second-started');
195
+ const thirdMarker = join(home, 'third-started');
196
+ const localDir = join(home, '.local/bin');
197
+ const staicliDir = join(home, '.staicli/current');
198
+ const emptyPath = join(root, 'empty-path');
199
+ const { mkdirSync } = await import('node:fs');
200
+ mkdirSync(localDir, { recursive: true });
201
+ mkdirSync(staicliDir, { recursive: true });
202
+ mkdirSync(emptyPath, { recursive: true });
203
+ const candidateScript = (startedMarker) => `#!${process.execPath}
204
+ require('node:fs').writeFileSync(${JSON.stringify(startedMarker)},'started');
205
+ setInterval(()=>{},1000);`;
206
+ writeFileSync(join(localDir, 'hbcli'), candidateScript(secondMarker), { mode: 0o700 });
207
+ writeFileSync(join(staicliDir, 'hbcli'), candidateScript(thirdMarker), { mode: 0o700 });
208
+ const savedHome = process.env.HOME;
209
+ const savedPath = process.env.PATH;
210
+ process.env.HOME = home;
211
+ process.env.PATH = emptyPath;
212
+ const candidateAbort = new AbortController();
213
+ const candidateWork = callHbcliJson([], { hbcliBin: 'hbcli', timeoutMs: 10_000, signal: candidateAbort.signal });
214
+ try {
215
+ await waitForFile(secondMarker, 'candidate process');
216
+ candidateAbort.abort();
217
+ const candidateResult = await candidateWork;
218
+ assert.equal(candidateResult.error, 'aborted by host signal');
219
+ assert.equal(existsSync(thirdMarker), false, 'abort must not continue to another candidate');
220
+ }
221
+ finally {
222
+ candidateAbort.abort();
223
+ await candidateWork;
224
+ process.env.HOME = savedHome;
225
+ process.env.PATH = savedPath;
226
+ }
227
+ // 3) 超时:SIGKILL 树,语义 timedOut
228
+ const slow = join(root, 'slow.js');
229
+ writeFileSync(slow, `#!${process.execPath}
230
+ const t=setInterval(()=>{},1000);`, { mode: 0o700 });
231
+ const timed = await spawnBounded(slow, [], { env: process.env, timeoutMs: 300 });
232
+ assert.equal(timed.timedOut, true, JSON.stringify(timed));
233
+ assert.equal(timed.aborted, false);
234
+ assert.equal(timed.groupReaped, true, JSON.stringify(timed));
235
+ assert.match(timed.error ?? '', /timeout/);
236
+ console.log(`SPAWN BOUNDED ABORT TESTS: 8/8 OK(pre-abort 零 spawn/取消不静态降级/abort 有界杀真父子树/escaped-pipe settlement/adapter cleanup error/abort 不换候选/timeout SIGKILL 树/语义区分)`);
237
+ }
238
+ finally {
239
+ activeAbort?.abort();
240
+ if (activeWork)
241
+ await activeWork;
242
+ cleanupOwnedProcesses();
243
+ rmSync(root, { recursive: true, force: true });
244
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * sponsor 插件与同内核端到端复用证明测试(issue #235;全离线 fixture 合成数据,零真实调用):
3
+ * 1. 插件配置 fail-closed(反例先行):缺 sponsorId/travelerPrincipalId/bffPrincipalId、
4
+ * 空路由表/重复路由、缺披露文案或缺排序偏置声明、负/非整数佣金、sponsorId 冒充 traveler
5
+ * principal(三主体混同)——全部构造即拒
6
+ * 2. 运行激活默认关闭:SPONSOR_RUNTIME_ACTIVATION_DEFAULT=false,requireRuntimeActivation(false) 抛错,
7
+ * 显式 true 才放行(本件为结构性证明,非运行时激活,零真实交易路径)
8
+ * 3. 路由授权(越权反例):路由不在 sponsor allowedRoutes 闭集 → route-unauthorized;
9
+ * 授权路由但缺 session/order 绑定 → missing-session-binding(禁止复制 Buyer/credentials 规避)
10
+ * 4. 同内核引用证明(复用面):sponsor 模块源码必须 import 自 src/booking-saga.ts 与 src/wish-pool.ts
11
+ * (同源调用,零拷贝——不重声明边表/解析器);运行时面 ts/src/** 零 sponsor-plugin 引用
12
+ * (C 端默认路径结构性隔离);内核边表/拒绝表十二格从消费侧逐字钉死(内核漂移即本套件红)
13
+ * 5. 排序红线:高佣金候选不满足旅行者条件 → 保持排除,赞助权重不可伪造用户效用;
14
+ * 同分 tie-break 只按申报顺位且必须显式披露偏置;佣金数值从不进入排序键
15
+ * 6. 端到端 B2B(fixture):配置→路由授权→库存/规划(内核评分)→确认闸
16
+ * (write.pending→write.confirmed,披露 digest 进入确认指纹,必携 receipt)→审计/回放
17
+ * (内核 sagaTraceViolations 空违例)
18
+ * 7. 端到端 B2C + 隔离:同一 runner 不带 plugin → b2c 形态;saga 边序列与 B2B 逐格相同
19
+ * (同一内核函数);leak 检查器零发现(sponsor 字段零渗入);空 receipt 两种形态同拒
20
+ * (同一 WriteGate 规则,无豁免)
21
+ * 8. A/B 同业务 id 不串:同 businessId 不同 sponsor → scope 三元组隔离,事件/指纹/approval
22
+ * 互不可见;A 的 approval 绑定在 B 的指纹上校验失败;取消退款路径(propose→confirm→compensate)
23
+ * 在 sponsor 形态同样走内核边表,回放零违例
24
+ *
25
+ * 运行: cd ts && npx tsx scripts/sponsor-reuse-tests.ts
26
+ */
27
+ import assert from 'node:assert/strict';
28
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
29
+ import { join } from 'node:path';
30
+ import { SAGA_EDGES, SAGA_REJECTIONS, } from '../src/booking-saga.js';
31
+ import { SPONSOR_PLUGIN_SCHEMA, SPONSOR_RUNTIME_ACTIVATION_DEFAULT, authorizeSponsorRoute, buildConfirmationFingerprint, bindApproval, createSponsorPlugin, rankSponsorCandidates, requireRuntimeActivation, runBookingScenario, scoreCandidateAgainstWish, sponsorLeakFindings, verifyApprovalBinding, } from '../capabilities/sponsor-plugin.js';
32
+ const REPO_TS = new URL('..', import.meta.url).pathname;
33
+ // ── fixture:两个 sponsor 配置 + 三条合成库存候选 ─────────────────────────────
34
+ function sponsorConfigA() {
35
+ return {
36
+ sponsorId: 'sponsor-a-travel',
37
+ travelerPrincipalId: 'traveler-001',
38
+ bffPrincipalId: 'bff-agency-a',
39
+ allowedRoutes: ['route.packaged-erhai-4d', 'route.packaged-yunnan-5d'],
40
+ disclosure: {
41
+ commissionBps: 300,
42
+ statement: '本方案由旅行社 A 提供库存,成交后旅行社获得 3% 佣金。',
43
+ rankingBiasNotice: '同分候选按旅行社申报顺位排序;佣金不影响条件判定。',
44
+ },
45
+ };
46
+ }
47
+ function sponsorConfigB() {
48
+ const c = sponsorConfigA();
49
+ return { ...c, sponsorId: 'sponsor-b-board', bffPrincipalId: 'bff-board-b', disclosure: { ...c.disclosure, commissionBps: 0 } };
50
+ }
51
+ function makePlugin(overrides = {}) {
52
+ return createSponsorPlugin({ ...sponsorConfigA(), ...overrides });
53
+ }
54
+ const WISH = { days: 4, budgetCny: 8000, month: 7 };
55
+ function candidates() {
56
+ return [
57
+ {
58
+ candidateRef: 'cand-alpha',
59
+ route: 'route.packaged-erhai-4d',
60
+ name: '大理洱海 4 日包价',
61
+ priceCny: 6500,
62
+ conditions: { days: 4, budget_cny: 7000, best_months: [7] },
63
+ commissionBps: 300,
64
+ },
65
+ {
66
+ candidateRef: 'cand-beta',
67
+ route: 'route.packaged-yunnan-5d',
68
+ name: '云南 5 日(旅行者只有 4 天)',
69
+ priceCny: 7200,
70
+ conditions: { days: 5, budget_cny: 7000, best_months: [7] },
71
+ commissionBps: 900,
72
+ },
73
+ ];
74
+ }
75
+ // ── 1. 插件配置 fail-closed(反例先行) ───────────────────────────────────────
76
+ for (const [field, broken] of [
77
+ ['sponsorId', { sponsorId: '' }],
78
+ ['travelerPrincipalId', { travelerPrincipalId: ' ' }],
79
+ ['bffPrincipalId', { bffPrincipalId: '' }],
80
+ ['allowedRoutes-empty', { allowedRoutes: [] }],
81
+ ['allowedRoutes-dup', { allowedRoutes: ['route.a', 'route.a'] }],
82
+ ['statement', { disclosure: { commissionBps: 100, statement: '', rankingBiasNotice: 'x' } }],
83
+ ['rankingBiasNotice', { disclosure: { commissionBps: 100, statement: 'x', rankingBiasNotice: '' } }],
84
+ ['commissionBps-negative', { disclosure: { commissionBps: -1, statement: 'x', rankingBiasNotice: 'y' } }],
85
+ ['commissionBps-fraction', { disclosure: { commissionBps: 1.5, statement: 'x', rankingBiasNotice: 'y' } }],
86
+ ]) {
87
+ assert.throws(() => makePlugin(broken), Error, `缺/坏 ${field} → 拒绝建插件(fail-closed)`);
88
+ }
89
+ assert.throws(() => makePlugin({ travelerPrincipalId: 'sponsor-a-travel' }), Error, 'sponsorId 冒充 traveler principal(三主体混同)→ 拒绝');
90
+ const pluginA = makePlugin();
91
+ assert.equal(pluginA.schemaVersion, SPONSOR_PLUGIN_SCHEMA, '插件 schema = sponsor.plugin.v1');
92
+ assert.match(pluginA.disclosureDigestSha256, /^[0-9a-f]{64}$/, '披露口径摘要为 sha256(佣金/文案/偏置声明进 digest)');
93
+ // ── 2. 运行激活默认关闭 ─────────────────────────────────────────────────────
94
+ assert.equal(SPONSOR_RUNTIME_ACTIVATION_DEFAULT, false, '激活常量默认 false(结构性默认关)');
95
+ assert.throws(() => requireRuntimeActivation(SPONSOR_RUNTIME_ACTIVATION_DEFAULT), Error, '默认态调用激活门 → 抛错');
96
+ assert.doesNotThrow(() => requireRuntimeActivation(true), '显式 opt-in 才放行(真实运行时接线属 M6 Entry 后)');
97
+ // ── 3. 路由授权(越权反例) ──────────────────────────────────────────────────
98
+ const unauthorized = authorizeSponsorRoute(pluginA, 'route.not-declared', { sessionRef: 'sess-1' });
99
+ assert.equal(unauthorized.ok, false, '越权路由 → 拒绝');
100
+ assert.equal(unauthorized.ok ? '' : unauthorized.reason, 'route-unauthorized', '拒绝理由 = route-unauthorized');
101
+ const unbound = authorizeSponsorRoute(pluginA, 'route.packaged-erhai-4d', { sessionRef: null });
102
+ assert.equal(unbound.ok ? '' : unbound.reason, 'missing-session-binding', '授权路由但缺 session 绑定 → 拒绝(禁止复制 Buyer/credentials 规避)');
103
+ const okRoute = authorizeSponsorRoute(pluginA, 'route.packaged-erhai-4d', { sessionRef: 'sess-traveler-001', orderRef: 'ord-fix-1' });
104
+ assert.equal(okRoute.ok, true, '闭集内路由 + session/order 绑定 → 授权通过');
105
+ const pluginB = makePlugin(sponsorConfigB());
106
+ const crossRoute = authorizeSponsorRoute(pluginB, 'route.packaged-yunnan-5d', { sessionRef: 'sess-b' });
107
+ assert.equal(crossRoute.ok, true, '每个 sponsor 的授权闭集独立维护');
108
+ // ── 4. 同内核引用证明(复用面 = 零拷贝 + 零内核改动 + C 端隔离) ───────────────
109
+ // 4a. 内核契约从消费侧钉死:12 格 (origin,trigger) 边表/拒绝表与冻结字面量逐字一致
110
+ const EDGE_GOLDEN = [
111
+ 'none:propose>pending:write.pending',
112
+ 'pending:confirm>confirmed:write.confirmed',
113
+ 'pending:compensate>compensated:write.compensated',
114
+ 'confirmed:compensate>compensated:write.compensated',
115
+ ];
116
+ assert.equal(SAGA_EDGES.map((e) => `${e.from}:${e.trigger}>${e.to}:${e.event}`).join('|'), EDGE_GOLDEN.join('|'), '内核 booking_saga_fsm.v1 边表与冻结字面量逐字一致(内核漂移即红)');
117
+ const REJECT_GOLDEN = [
118
+ 'none:confirm', 'none:compensate', 'pending:propose', 'confirmed:propose',
119
+ 'confirmed:confirm', 'compensated:propose', 'compensated:confirm', 'compensated:compensate',
120
+ ];
121
+ assert.equal(SAGA_REJECTIONS.map((e) => `${e.from}:${e.trigger}`).join('|'), REJECT_GOLDEN.join('|'), '内核拒绝闭集八格与冻结字面量逐字一致');
122
+ // 4b. sponsor 模块同源 import(零拷贝:不重声明内核边表/解析器/评分器)
123
+ const sponsorSrc = readFileSync(join(REPO_TS, 'capabilities/sponsor-plugin.ts'), 'utf8');
124
+ assert.match(sponsorSrc, /from '\.\.\/src\/booking-saga\.ts'/, 'sponsor 模块 import 自内核 booking-saga.ts(同一内核函数)');
125
+ assert.match(sponsorSrc, /from '\.\.\/src\/wish-pool\.ts'/, 'sponsor 模块 import 自内核 wish-pool.ts(同一评分函数)');
126
+ assert.doesNotMatch(sponsorSrc, /function resolveSagaTrigger/, '零拷贝:不重声明内核触发解析器');
127
+ assert.doesNotMatch(sponsorSrc, /const SAGA_EDGES/, '零拷贝:不重声明内核边表');
128
+ assert.doesNotMatch(sponsorSrc, /function scoreWishMatch/, '零拷贝:不重声明内核评分器');
129
+ // 4c. 运行时面隔离:ts/src/** 无任何文件引用 sponsor-plugin(C 端默认路径结构性零渗入)
130
+ function walkTs(dir) {
131
+ return readdirSync(dir).flatMap((name) => {
132
+ const p = join(dir, name);
133
+ if (statSync(p).isDirectory())
134
+ return walkTs(p);
135
+ return name.endsWith('.ts') ? [p] : [];
136
+ });
137
+ }
138
+ const runtimeFiles = walkTs(join(REPO_TS, 'src'));
139
+ assert.ok(runtimeFiles.length > 20, `运行时面文件枚举非空(实得 ${runtimeFiles.length})`);
140
+ const leaked = runtimeFiles.filter((f) => readFileSync(f, 'utf8').includes('sponsor-plugin'));
141
+ assert.deepEqual(leaked, [], 'ts/src/** 零 sponsor-plugin 引用(C 端默认路径不接线 sponsor 面)');
142
+ // ── 5. 排序红线(赞助收益不伪造用户效用) ────────────────────────────────────
143
+ const ranking = rankSponsorCandidates(pluginA, candidates(), WISH);
144
+ assert.deepEqual(ranking.excluded.map((e) => `${e.candidateRef}:${e.why}`), ['cand-beta:conditions-not-met'], '高佣金(900bps)但天数不足的候选保持排除——佣金不可赎回条件');
145
+ assert.equal(ranking.ranked.length, 1, '只有条件全命中的候选进入排序');
146
+ assert.equal(ranking.ranked[0].candidate.candidateRef, 'cand-alpha', '命中候选 = cand-alpha');
147
+ assert.equal(ranking.ranked[0].sponsorTieBreakApplied, false, '无同分不发生赞助 tie-break');
148
+ assert.ok(ranking.ranked[0].disclosure.statement.length > 0, '排序结果必携披露文案');
149
+ assert.ok(ranking.biasNotice.length > 0, '排序输出顶层必携偏置声明(渲染面必须可见)');
150
+ // 同分 tie:按申报顺位,不按佣金;发生 tie-break 必须显式披露
151
+ const tieCandidates = [
152
+ { candidateRef: 'tie-low', route: 'route.packaged-erhai-4d', name: '低佣金', priceCny: 6600, conditions: { days: 4, budget_cny: 7000 }, commissionBps: 100 },
153
+ { candidateRef: 'tie-high', route: 'route.packaged-yunnan-5d', name: '高佣金同分', priceCny: 6700, conditions: { days: 4, budget_cny: 7000 }, commissionBps: 900 },
154
+ ];
155
+ const tieRank = rankSponsorCandidates(pluginA, tieCandidates, { days: 4, budgetCny: 8000 });
156
+ assert.deepEqual(tieRank.ranked.map((r) => r.candidate.candidateRef), ['tie-low', 'tie-high'], '同分按申报顺位(申报顺位在前的低佣金居首)');
157
+ assert.equal(tieRank.ranked[0].sponsorTieBreakApplied, false, '申报顺位首位无需 tie-break 标记');
158
+ const tieReversed = rankSponsorCandidates(pluginA, [tieCandidates[1], tieCandidates[0]], { days: 4, budgetCny: 8000 });
159
+ assert.deepEqual(tieReversed.ranked.map((r) => r.candidate.candidateRef), ['tie-high', 'tie-low'], '调换申报顺位则高佣金居首——排序键是申报顺位而非佣金');
160
+ assert.equal(tieReversed.ranked[0].sponsorTieBreakApplied, true, 'tie-break 真实发生时必须标记');
161
+ assert.ok(tieReversed.ranked[0].disclosure.rankingBiasNotice.length > 0, 'tie-break 候选必携偏置声明(偏置必须展示给旅行者)');
162
+ // 内核评分器直证:同一 scoreCandidateAgainstWish 在两形态共用(条件不满足 → null)
163
+ assert.equal(scoreCandidateAgainstWish(candidates()[1], WISH), null, '内核评分:天数不足 → null(不可预订)');
164
+ assert.equal(scoreCandidateAgainstWish(candidates()[0], WISH)?.hits.length, 3, '内核评分:三条件全命中');
165
+ // ── 6. 端到端 B2B(fixture 合成数据,零真实调用) ─────────────────────────────
166
+ const b2bTrace = runBookingScenario({
167
+ businessId: 'BIZ-1',
168
+ travelerPrincipalId: 'traveler-001',
169
+ sessionRef: 'sess-traveler-001',
170
+ candidates: candidates(),
171
+ wish: WISH,
172
+ action: 'confirm',
173
+ receipt: 'receipt-hb-fix-001',
174
+ plugin: pluginA,
175
+ });
176
+ assert.equal(b2bTrace.form, 'b2b-sponsor', 'B2B 形态标记 b2b-sponsor');
177
+ assert.equal(b2bTrace.selectedCandidateRef, 'cand-alpha', '选中条件全命中候选');
178
+ const b2bKinds = b2bTrace.events.map((e) => e.kind);
179
+ assert.deepEqual(b2bKinds, ['route.authorized', 'candidate.ranked', 'write.pending', 'write.confirmed'], 'B2B 事件序列:路由授权→排序→propose→confirm');
180
+ const b2bPending = b2bTrace.events[2];
181
+ const b2bConfirmed = b2bTrace.events[3];
182
+ assert.equal(b2bTrace.sagaStatus, 'confirmed', 'saga 终态 confirmed');
183
+ assert.ok(b2bPending.guard !== null && b2bPending.guard.includes('idem_key UNIQUE'), 'write.pending 携内核边表守卫原文(同一张边表)');
184
+ assert.ok(b2bConfirmed.guard !== null && b2bConfirmed.guard.includes('receipt'), 'write.confirmed 携内核边表守卫原文(L3 具名确认)');
185
+ assert.equal(b2bConfirmed.receipt, 'receipt-hb-fix-001', '确认必携外部回执');
186
+ assert.deepEqual(b2bTrace.sagaViolations, [], '内核 sagaTraceViolations 回放零违例(审计链合法)');
187
+ // 披露 digest 进入确认指纹:同一请求换披露 → 指纹必变
188
+ const fpA = buildConfirmationFingerprint({
189
+ businessId: 'BIZ-1', travelerPrincipalId: 'traveler-001', sponsorId: 'sponsor-a-travel',
190
+ candidateRef: 'cand-alpha', route: 'route.packaged-erhai-4d', priceCny: 6500,
191
+ disclosureDigestSha256: pluginA.disclosureDigestSha256,
192
+ });
193
+ const fpA2 = buildConfirmationFingerprint({
194
+ businessId: 'BIZ-1', travelerPrincipalId: 'traveler-001', sponsorId: 'sponsor-a-travel',
195
+ candidateRef: 'cand-alpha', route: 'route.packaged-erhai-4d', priceCny: 6500,
196
+ disclosureDigestSha256: '0'.repeat(64),
197
+ });
198
+ assert.notEqual(fpA, fpA2, '披露口径变化 → 确认指纹变化(佣金披露进入确认指纹)');
199
+ assert.ok(b2bConfirmed.fingerprintSha256 && b2bConfirmed.fingerprintSha256.length === 64, '确认事件携带 64 位指纹');
200
+ const approval = bindApproval({ fingerprintSha256: fpA, receipt: 'receipt-hb-fix-001', travelerPrincipalId: 'traveler-001', sponsorId: 'sponsor-a-travel' });
201
+ assert.equal(verifyApprovalBinding(fpA, approval), true, 'approval 绑定同指纹校验通过');
202
+ assert.equal(verifyApprovalBinding(fpA2, approval), false, '指纹不匹配 → approval 绑定校验失败');
203
+ // ── 7. 端到端 B2C + 隔离(同一 runner,零 sponsor 渗入) ──────────────────────
204
+ const b2cTrace = runBookingScenario({
205
+ businessId: 'BIZ-1',
206
+ travelerPrincipalId: 'traveler-001',
207
+ candidates: candidates(),
208
+ wish: WISH,
209
+ action: 'confirm',
210
+ receipt: 'receipt-hb-fix-001',
211
+ });
212
+ assert.equal(b2cTrace.form, 'b2c', '无 plugin → C 端默认形态');
213
+ assert.equal(b2cTrace.selectedCandidateRef, 'cand-alpha', 'C 端同一内核评分选中同一候选(复用)');
214
+ assert.deepEqual(b2cTrace.events.map((e) => e.kind), ['candidate.ranked', 'write.pending', 'write.confirmed'], 'C 端无路由授权/披露事件');
215
+ // 同一内核边序列:两形态 saga 段逐格相同(from/to/event/guard)
216
+ const sagaSlice = (t) => t.events
217
+ .filter((e) => e.sagaEvent !== null)
218
+ .map((e) => `${e.kind}:${e.from}>${e.to}:${(e.guard ?? '').slice(0, 12)}`);
219
+ assert.deepEqual(sagaSlice(b2bTrace), sagaSlice(b2cTrace), 'B2B/B2C saga 段逐格相同——同一内核函数在 sponsor 形态下跑通同一状态机');
220
+ assert.deepEqual(sponsorLeakFindings(b2cTrace), [], 'C 端 trace 泄漏检查零发现(sponsor 字段零渗入)');
221
+ assert.ok(sponsorLeakFindings(b2bTrace).length > 0, '对照:B2B trace 泄漏检查有发现(检查器本身有效)');
222
+ // 空 receipt 两种形态同拒(同一 WriteGate/空 receipt 红线,无豁免)
223
+ for (const [form, opts] of [
224
+ ['b2b', { plugin: pluginA, sessionRef: 'sess-traveler-001' }],
225
+ ['b2c', {}],
226
+ ]) {
227
+ assert.throws(() => runBookingScenario({
228
+ businessId: 'BIZ-1', travelerPrincipalId: 'traveler-001',
229
+ candidates: candidates(), wish: WISH, action: 'confirm',
230
+ receipt: ' ', ...opts,
231
+ }), Error, `${form} 空 receipt → 拒绝(空 receipt 红线无 B2B 豁免)`);
232
+ }
233
+ assert.throws(() => runBookingScenario({
234
+ businessId: 'BIZ-1', travelerPrincipalId: 'traveler-001', sessionRef: 'sess-x',
235
+ candidates: [{ ...candidates()[0], route: 'route.not-declared' }], wish: WISH,
236
+ action: 'confirm', receipt: 'r-1', plugin: pluginA,
237
+ }), Error, 'B2B 端到端越权路由 → fail-closed 中止(不静默换路由)');
238
+ // ── 8. A/B 同业务 id 不串(事件/投影/approval/effect) ────────────────────────
239
+ const traceA = b2bTrace;
240
+ const traceB = runBookingScenario({
241
+ businessId: 'BIZ-1',
242
+ travelerPrincipalId: 'traveler-001',
243
+ sessionRef: 'sess-board-b',
244
+ candidates: candidates(),
245
+ wish: WISH,
246
+ action: 'cancel',
247
+ receipt: 'receipt-hb-fix-002',
248
+ plugin: pluginB,
249
+ });
250
+ assert.equal(traceA.scope.sponsorId, 'sponsor-a-travel', 'A 事件 scope 携 sponsor-a');
251
+ assert.equal(traceB.scope.sponsorId, 'sponsor-b-board', 'B 事件 scope 携 sponsor-b');
252
+ assert.ok(traceA.events.every((e) => e.scope.sponsorId === 'sponsor-a-travel'), 'A 的全部事件归属 A(同业务 id 不串事件)');
253
+ assert.ok(traceB.events.every((e) => e.scope.sponsorId === 'sponsor-b-board'), 'B 的全部事件归属 B');
254
+ assert.notEqual(traceA.scope.key, traceB.scope.key, '同 businessId 不同 sponsor → scope 键不同(投影隔离)');
255
+ assert.equal(traceB.sagaStatus, 'compensated', 'B 走取消退款:propose→confirm→compensate');
256
+ assert.deepEqual(traceB.events.map((e) => e.kind), ['route.authorized', 'candidate.ranked', 'write.pending', 'write.confirmed', 'write.compensated'], '取消路径事件序列');
257
+ assert.deepEqual(traceB.sagaViolations, [], 'B 取消退款回放零违例(sponsor 形态同一取消/退款规则)');
258
+ // approval 不可跨 scope 重放:A 的 approval 绑到 B 的指纹 → 校验失败
259
+ const approvalB = bindApproval({ fingerprintSha256: traceB.events[3].fingerprintSha256, receipt: 'receipt-hb-fix-002', travelerPrincipalId: 'traveler-001', sponsorId: 'sponsor-b-board' });
260
+ assert.equal(verifyApprovalBinding(traceA.events[3].fingerprintSha256, approvalB), false, 'A 的指纹不接受 B 的 approval(BFF 重放/跨租户绑定拒绝)');
261
+ assert.deepEqual(traceA.sagaViolations, [], 'A 回放零违例');
262
+ console.log('SPONSOR REUSE TESTS: 8/8 OK(配置 fail-closed / 默认关激活门 / 越权路由拒绝 / 同内核零拷贝+C端隔离 / 佣金不伪造效用 / B2B fixture E2E+披露入指纹 / B2C 同 runner 零渗入+同 saga 边 / A/B 同业务id不串)');