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

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 (507) 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 +1744 -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 +135 -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 +546 -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 +509 -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/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Share contract 验收套件(issue #573,Phase C 切片;含 PR #574 自检 review 修复面):
3
+ * adapter 契约 + HMAC share token(target 绑定)+ consent state machine(幂等 grant)
4
+ * + shareDeck 编排(never-throws / 原型键防护 / adapter_error 收敛)
5
+ * + 校验面运行期边界(失效时钟不得放行;非字符串 secret 键拒绝;
6
+ * production 缺 secret 的 verify 收敛,签发面刻意抛错保留——配置面用
7
+ * 隔离子进程验证)。
8
+ *
9
+ * 全离线、合成数据,无网络、无 SDK 调用、无 stateRoot 写。
10
+ *
11
+ * 运行(在 ts/ 下):npx tsx scripts/share-tests.ts
12
+ */
13
+ import { spawnSync } from 'node:child_process';
14
+ import { createHmac } from 'node:crypto';
15
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { createRequire } from 'node:module';
17
+ import { tmpdir } from 'node:os';
18
+ import { dirname, join } from 'node:path';
19
+ import { pathToFileURL } from 'node:url';
20
+ import { ADAPTERS, SHARE_CHANNELS, iMessageAdapter, smsAdapter, slackAdapter, webhookAdapter, } from '../src/share/adapters.js';
21
+ import { DEFAULT_DEV_SHARE_SECRET, SHARE_TOKEN_TTL_MAX_SECONDS, resolveShareSecret, signShareToken, verifyShareToken, } from '../src/share/share-token.js';
22
+ import { checkShareConsent, defaultShareConsentState, grantShareConsent, revokeShareConsent, setShareConsentMode, } from '../src/share/share-consent.js';
23
+ import { shareDeck } from '../src/share/share-deck.js';
24
+ let pass = 0;
25
+ let fail = 0;
26
+ const failures = [];
27
+ function ok(cond, msg) {
28
+ if (cond) {
29
+ pass++;
30
+ return;
31
+ }
32
+ fail++;
33
+ failures.push(msg);
34
+ }
35
+ const SECRET = 'test-secret-DO-NOT-USE-IN-PROD';
36
+ const FUTURE = (() => {
37
+ const base = new Date('2026-09-23T12:00:00.000Z').getTime();
38
+ return (offsetMs) => () => new Date(base + offsetMs);
39
+ })();
40
+ const NOW = FUTURE(0);
41
+ const TARGET = { channel: 'imessage', address: '+1-555-0100' };
42
+ const TOKEN_PAYLOAD = {
43
+ share_id: 'deck-export-2027-thailand',
44
+ created_at: NOW().toISOString(),
45
+ ttl_seconds: 3600,
46
+ target: TARGET,
47
+ };
48
+ const REQUEST = {
49
+ target: TARGET,
50
+ body: { html_path: '/export/deck.html', manifest_path: '/export/deck.manifest.json', qr_path: '/export/deck.qr.svg', note: 'try this' },
51
+ };
52
+ /** 便捷:按 share_id + 目标地址造 token(通道默认 imessage) */
53
+ function tk(share_id, target = TARGET, ttl = 3600) {
54
+ return signShareToken({ share_id, created_at: NOW().toISOString(), ttl_seconds: ttl, target }, SECRET);
55
+ }
56
+ // ===========================================================================
57
+ // §1 adapter 契约层 — 4 个 stub + ADAPTERS 表冻结 + channel 闭集派生
58
+ // ===========================================================================
59
+ {
60
+ ok(SHARE_CHANNELS.length === 4, `§1a SHARE_CHANNELS 长度 = 4(实测 ${SHARE_CHANNELS.length})`);
61
+ for (const id of SHARE_CHANNELS) {
62
+ const a = ADAPTERS[id];
63
+ ok(typeof a?.send === 'function' && a?.id === id, `§1b ADAPTERS[${id}] 是合法 ShareAdapter`);
64
+ }
65
+ const expected = new Set(['imessage', 'sms', 'slack', 'webhook']);
66
+ ok(SHARE_CHANNELS.length === expected.size && SHARE_CHANNELS.every(c => expected.has(c)), '§1c 4 个通道名锁定,扩通道前先 review(避免字符串自由枚举)');
67
+ // 闭集从 ADAPTERS 键派生(单一事实源):键与列表一一对应
68
+ ok(JSON.stringify([...SHARE_CHANNELS].sort()) === JSON.stringify(Object.keys(ADAPTERS).sort()), '§1d SHARE_CHANNELS 与 ADAPTERS 键一一对应(派生,非手列)');
69
+ // ADAPTERS 运行时冻结(ESM 严格模式下赋值冻结对象抛 TypeError)
70
+ let froze = false;
71
+ try {
72
+ ;
73
+ ADAPTERS.sms = iMessageAdapter;
74
+ }
75
+ catch {
76
+ froze = true;
77
+ }
78
+ ok(froze && ADAPTERS.sms === smsAdapter, '§1e ADAPTERS 运行时冻结(改写抛错且原值不变)');
79
+ const stubResults = await Promise.all([iMessageAdapter, smsAdapter, slackAdapter, webhookAdapter].map(a => a.send({
80
+ token: 'irrelevant',
81
+ target: { channel: a.id, address: 'x' },
82
+ body: {},
83
+ })));
84
+ ok(stubResults.every(r => !r.delivered && r.reason === 'not_activated'), '§1f 4 个 stub 统一返回 not_activated(无副作用、无网络、无 SDK)');
85
+ }
86
+ // ===========================================================================
87
+ // §2 HMAC share token — 签/验 round-trip + 篡改 + 过期 + 格式闭契约
88
+ // ===========================================================================
89
+ {
90
+ const token = signShareToken(TOKEN_PAYLOAD, SECRET);
91
+ ok(typeof token === 'string' && token.includes('.') && token.length > 40, `§2a 签发 token 是非空 base64url.body.sig 字符串(${token.length} 字符)`);
92
+ const verify = verifyShareToken(token, SECRET, NOW);
93
+ ok(verify.ok && verify.payload.share_id === TOKEN_PAYLOAD.share_id
94
+ && verify.payload.ttl_seconds === 3600
95
+ && verify.ok && verify.payload.target.channel === 'imessage'
96
+ && verify.payload.target.address === '+1-555-0100', '§2b round-trip:share_id / ttl / target(通道+地址)逐字段保持');
97
+ const token2 = signShareToken(TOKEN_PAYLOAD, SECRET);
98
+ ok(token === token2, '§2c 签发字节级确定性(同输入 → 同 token)');
99
+ const tampered = token.slice(0, 5) + (token[5] === 'A' ? 'B' : 'A') + token.slice(6);
100
+ const tamperResult = verifyShareToken(tampered, SECRET, NOW);
101
+ ok(!tamperResult.ok && tamperResult.reason === 'token_invalid', '§2d 篡改 body → token_invalid');
102
+ const lastDot = token.lastIndexOf('.');
103
+ const sigTampered = token.slice(0, lastDot + 1) + (token[lastDot + 1] === 'A' ? 'B' : 'A') + token.slice(lastDot + 2);
104
+ const sigResult = verifyShareToken(sigTampered, SECRET, NOW);
105
+ ok(!sigResult.ok && sigResult.reason === 'token_invalid', '§2e 篡改 sig → token_invalid');
106
+ const wrongSecret = verifyShareToken(token, 'wrong-secret', NOW);
107
+ ok(!wrongSecret.ok && wrongSecret.reason === 'token_invalid', '§2f 不同 secret → token_invalid');
108
+ const shortLived = signShareToken({ share_id: 'short', created_at: NOW().toISOString(), ttl_seconds: 1, target: TARGET }, SECRET);
109
+ const expiredResult = verifyShareToken(shortLived, SECRET, () => new Date(NOW().getTime() + 5_000));
110
+ ok(!expiredResult.ok && expiredResult.reason === 'token_expired', '§2g 过期 → token_expired');
111
+ const zeroTtl = signShareToken({ share_id: 'z', created_at: NOW().toISOString(), ttl_seconds: 0, target: TARGET }, SECRET);
112
+ const zeroResult = verifyShareToken(zeroTtl, SECRET, () => new Date(NOW().getTime() + 1));
113
+ ok(!zeroResult.ok && zeroResult.reason === 'token_expired', '§2h ttl=0 边界:下一瞬即过期');
114
+ // 格式错:全部必须精确返回 token_invalid(不是 token_expired——格式与过期的区分是文档契约)
115
+ for (const bad of ['', '.', 'no-dot-here', 'a.', '.b', '..']) {
116
+ const r = verifyShareToken(bad, SECRET, NOW);
117
+ ok(!r.ok && r.reason === 'token_invalid', `§2i 格式错 ${JSON.stringify(bad)} → 精确 token_invalid`);
118
+ }
119
+ // 多点 token:合法 token + 尾随 '.xxx'(base64url 解码器会吞非法字符,必须显式拒)
120
+ const multiDot = `${token}.garbage`;
121
+ const multiDotResult = verifyShareToken(multiDot, SECRET, NOW);
122
+ ok(!multiDotResult.ok && multiDotResult.reason === 'token_invalid', '§2j 多点 token(尾随垃圾)→ token_invalid');
123
+ // ttl 溢出/非整数:1e308 会让 expiresMs=Infinity 永不过期;1.5 非整数
124
+ const overflowTtl = signShareToken({ share_id: 'ovf', created_at: NOW().toISOString(), ttl_seconds: 1e308, target: TARGET }, SECRET);
125
+ ok(!verifyShareToken(overflowTtl, SECRET, NOW).ok, '§2k ttl=1e308(溢出 Infinity)→ token_invalid');
126
+ const fracTtl = signShareToken({ share_id: 'frac', created_at: NOW().toISOString(), ttl_seconds: 1.5, target: TARGET }, SECRET);
127
+ ok(!verifyShareToken(fracTtl, SECRET, NOW).ok, '§2l ttl=1.5(非整数)→ token_invalid');
128
+ const maxTtl = signShareToken({ share_id: 'max', created_at: NOW().toISOString(), ttl_seconds: SHARE_TOKEN_TTL_MAX_SECONDS, target: TARGET }, SECRET);
129
+ ok(verifyShareToken(maxTtl, SECRET, NOW).ok, `§2m ttl=2^31-1(上界内)合法`);
130
+ // base64 不可解码
131
+ const badB64 = '!!!.!!!';
132
+ ok(!verifyShareToken(badB64, SECRET, NOW).ok, '§2n 不可解码的 base64 → 拒绝');
133
+ // 字段类型错(含缺 target)
134
+ const malformedPayload = Buffer.from(JSON.stringify({ share_id: 42, created_at: 'x', ttl_seconds: 'oops' }), 'utf-8').toString('base64url');
135
+ const sigForMalformed = createHmac('sha256', SECRET).update(malformedPayload).digest('base64url');
136
+ const malformedResult = verifyShareToken(`${malformedPayload}.${sigForMalformed}`, SECRET, NOW);
137
+ ok(!malformedResult.ok && malformedResult.reason === 'token_invalid', '§2o 字段类型错(含缺 target)→ token_invalid');
138
+ // 有 target 但字段类型错
139
+ const badTarget = Buffer.from(JSON.stringify({ share_id: 'bt', created_at: NOW().toISOString(), ttl_seconds: 60, target: { channel: 42, address: [] } }), 'utf-8').toString('base64url');
140
+ const sigBadTarget = createHmac('sha256', SECRET).update(badTarget).digest('base64url');
141
+ ok(!verifyShareToken(`${badTarget}.${sigBadTarget}`, SECRET, NOW).ok, '§2p target 字段类型错 → token_invalid');
142
+ }
143
+ // ===========================================================================
144
+ // §3 consent state machine — 三态 + grant(幂等)/ revoke / check(按卡匹配)
145
+ // ===========================================================================
146
+ {
147
+ const s0 = defaultShareConsentState();
148
+ ok(s0.mode === 'ask' && s0.granted.length === 0 && s0.revoked.length === 0, '§3a 默认 state:ask + 0 granted + 0 revoked');
149
+ const r1 = checkShareConsent(s0, 'share-x');
150
+ ok(!r1.ok && r1.reason === 'consent_required', '§3b 无卡 → consent_required');
151
+ const granted = grantShareConsent(s0, 'share-x', NOW);
152
+ ok(granted.state.granted.length === 1, '§3c grant 后 state.granted 增 1');
153
+ ok(/^[0-9a-f-]{36}$/i.test(granted.card.card_id), `§3d card_id 是 UUID(实测 ${granted.card.card_id})`);
154
+ // 幂等:同 share 重复 grant 复用同一张卡(不追加)
155
+ const grantedAgain = grantShareConsent(granted.state, 'share-x', NOW);
156
+ ok(grantedAgain.state.granted.length === 1 && grantedAgain.card.card_id === granted.card.card_id, '§3e grant 幂等:同 share 重复 grant 复用存活卡(不追加,防多卡漂移)');
157
+ const r2 = checkShareConsent(granted.state, 'share-x', granted.card.card_id);
158
+ ok(r2.ok, '§3f 发卡后 checkShareConsent(同 share_id, 同 card_id) → ok');
159
+ // 出示伪造卡 id(不属于该 share)→ consent_revoked
160
+ const rForeign = checkShareConsent(granted.state, 'share-x', 'bogus-card-id');
161
+ ok(!rForeign.ok && rForeign.reason === 'consent_revoked', '§3g 出示不属于该 share 的 card_id → consent_revoked');
162
+ const revoked = revokeShareConsent(granted.state, granted.card.card_id);
163
+ const r3 = checkShareConsent(revoked, 'share-x', granted.card.card_id);
164
+ ok(!r3.ok && r3.reason === 'consent_revoked', '§3h 撤回后 → consent_revoked');
165
+ const r3b = checkShareConsent(revoked, 'share-x');
166
+ ok(!r3b.ok && r3b.reason === 'consent_revoked', '§3i 撤回后(不出示卡)→ consent_revoked(全撤即死)');
167
+ const revoked2 = revokeShareConsent(revoked, granted.card.card_id);
168
+ ok(revoked2.revoked.length === revoked.revoked.length, '§3j 重复 revoke 幂等');
169
+ // 撤光后再 grant:发新卡且复活
170
+ const reGranted = grantShareConsent(revoked, 'share-x', NOW);
171
+ ok(reGranted.state.granted.length === 2 && reGranted.card.card_id !== granted.card.card_id, '§3k 全撤后重新 grant:追加新卡(旧卡仍 revoked,新卡存活)');
172
+ const off = setShareConsentMode(granted.state, 'off');
173
+ const r4 = checkShareConsent(off, 'share-x', granted.card.card_id);
174
+ ok(!r4.ok && r4.reason === 'share_consent_off', '§3l off 总开关 → 一律 share_consent_off');
175
+ const allow = setShareConsentMode(s0, 'allow');
176
+ const r5 = checkShareConsent(allow, 'share-x');
177
+ ok(!r5.ok && r5.reason === 'consent_required', '§3m allow + 缺卡 → consent_required(同 ask)');
178
+ const grantedAtAllow = grantShareConsent(allow, 'share-x', NOW);
179
+ const r6 = checkShareConsent(grantedAtAllow.state, 'share-x', grantedAtAllow.card.card_id);
180
+ ok(r6.ok, '§3n allow + 有卡 → ok');
181
+ const r8 = checkShareConsent(granted.state, 'other-share', granted.card.card_id);
182
+ ok(!r8.ok && r8.reason === 'consent_required', '§3o 不同 share_id → consent_required(卡按 share 索引)');
183
+ }
184
+ // ===========================================================================
185
+ // §4 shareDeck 主流程 — token → target 绑定 → consent → channel → adapter
186
+ // ===========================================================================
187
+ {
188
+ const state = defaultShareConsentState();
189
+ const token = tk('deck-export-2027-thailand');
190
+ const r1 = await shareDeck('', REQUEST, { secret: SECRET, state, now: NOW });
191
+ ok(!r1.delivered && r1.reason === 'token_invalid', '§4a 空 token → token_invalid');
192
+ const r2 = await shareDeck('a.b', REQUEST, { secret: SECRET, state, now: NOW });
193
+ ok(!r2.delivered && r2.reason === 'token_invalid', '§4b 篡改 token → token_invalid');
194
+ const expired = tk('deck-export-2027-thailand', TARGET, 1);
195
+ const r3 = await shareDeck(expired, REQUEST, { secret: SECRET, state, now: () => new Date(NOW().getTime() + 5_000) });
196
+ ok(!r3.delivered && r3.reason === 'token_expired', '§4c 过期 token → token_expired');
197
+ // target 绑定:合法 token + 不同目的地(换收件人重放)→ token_invalid
198
+ const swappedTarget = await shareDeck(token, { ...REQUEST, target: { channel: 'slack', address: 'other@x' } }, { secret: SECRET, state, now: NOW });
199
+ ok(!swappedTarget.delivered && swappedTarget.reason === 'token_invalid', '§4d 合法 token 换收件人(channel+address 不符)→ token_invalid(防重放)');
200
+ // 仅换地址同样拒
201
+ const swappedAddr = await shareDeck(token, { ...REQUEST, target: { channel: 'imessage', address: '+1-999' } }, { secret: SECRET, state, now: NOW });
202
+ ok(!swappedAddr.delivered && swappedAddr.reason === 'token_invalid', '§4e 合法 token 换地址 → token_invalid');
203
+ const r4 = await shareDeck(token, REQUEST, { secret: SECRET, state, now: NOW });
204
+ ok(!r4.delivered && r4.reason === 'consent_required', '§4f token ok + 无卡 → consent_required');
205
+ const granted = grantShareConsent(state, 'deck-export-2027-thailand', NOW);
206
+ const r5 = await shareDeck(token, REQUEST, { secret: SECRET, state: granted.state, now: NOW });
207
+ ok(!r5.delivered && r5.reason === 'not_activated', '§4g 发卡 + 命中 adapter → not_activated(stub)');
208
+ // deps.card_id 二次校验接通:出示错误卡 → consent_revoked
209
+ const r5b = await shareDeck(token, REQUEST, { secret: SECRET, state: granted.state, now: NOW, card_id: 'bogus' });
210
+ ok(!r5b.delivered && r5b.reason === 'consent_revoked', '§4h deps.card_id 出示错误卡 → consent_revoked(二次校验接通)');
211
+ // 出示正确卡 → 通过(到 adapter)
212
+ const r5c = await shareDeck(token, REQUEST, { secret: SECRET, state: granted.state, now: NOW, card_id: granted.card.card_id });
213
+ ok(!r5c.delivered && r5c.reason === 'not_activated', '§4i deps.card_id 出示正确卡 → 通过到 adapter');
214
+ const revoked = revokeShareConsent(granted.state, granted.card.card_id);
215
+ const r6 = await shareDeck(token, REQUEST, { secret: SECRET, state: revoked, now: NOW });
216
+ ok(!r6.delivered && r6.reason === 'consent_revoked', '§4j 撤回后再 share → consent_revoked');
217
+ const off = setShareConsentMode(granted.state, 'off');
218
+ const r7 = await shareDeck(token, REQUEST, { secret: SECRET, state: off, now: NOW });
219
+ ok(!r7.delivered && r7.reason === 'share_consent_off', '§4k off 总开关 → share_consent_off');
220
+ // 原型键通道:不崩溃,精确 unknown_channel
221
+ for (const proto of ['__proto__', 'constructor', 'toString']) {
222
+ const g = grantShareConsent(state, `proto-${proto}`, NOW);
223
+ const t = tk(`proto-${proto}`, { channel: 'imessage', address: 'x' });
224
+ // 用绑定 imessage 的 token 但请求 proto 通道会先被 target 绑定拦;直接构造请求 target=proto
225
+ const req = { target: { channel: proto, address: 'x' }, body: {} };
226
+ // token 的 target 也要是 proto 才能走到分发——手工签一个
227
+ const protoToken = signShareToken({ share_id: `proto-${proto}`, created_at: NOW().toISOString(), ttl_seconds: 60, target: { channel: proto, address: 'x' } }, SECRET);
228
+ let threw = false;
229
+ let result = {};
230
+ try {
231
+ result = await shareDeck(protoToken, req, { secret: SECRET, state: g.state, now: NOW });
232
+ }
233
+ catch {
234
+ threw = true;
235
+ }
236
+ ok(!threw && !result.delivered && result.reason === 'unknown_channel', `§4l 原型键通道 ${proto} → 不崩溃,精确 unknown_channel`);
237
+ void t;
238
+ }
239
+ // 未知普通通道
240
+ const grantedAny = grantShareConsent(state, 'any-share', NOW);
241
+ const tkUnknown = tk('any-share', { channel: 'nonsense', address: 'x' });
242
+ const r8 = await shareDeck(tkUnknown, { ...REQUEST, target: { channel: 'nonsense', address: 'x' } }, { secret: SECRET, state: grantedAny.state, now: NOW });
243
+ ok(!r8.delivered && r8.reason === 'unknown_channel', '§4m 未知普通通道 → unknown_channel');
244
+ // 全 4 通道命中
245
+ for (const ch of SHARE_CHANNELS) {
246
+ const s = grantShareConsent(state, `share-${ch}`, NOW);
247
+ const t = tk(`share-${ch}`, { channel: ch, address: 'a@b.c' });
248
+ const r = await shareDeck(t, { target: { channel: ch, address: 'a@b.c' }, body: {} }, { secret: SECRET, state: s.state, now: NOW });
249
+ ok(!r.delivered && r.reason === 'not_activated', `§4n 通道 ${ch} 命中:stub 返回 not_activated`);
250
+ }
251
+ }
252
+ // ===========================================================================
253
+ // §5 never-throws + 字节级确定性 + 8 类拒绝全覆盖
254
+ // ===========================================================================
255
+ {
256
+ const state = defaultShareConsentState();
257
+ const token = tk('deck-export-2027-thailand');
258
+ const granted = grantShareConsent(state, 'deck-export-2027-thailand', NOW);
259
+ let threw = false;
260
+ try {
261
+ await shareDeck(token, { target: { channel: 'imessage', address: 'oops' }, body: {} }, { secret: SECRET, state: granted.state, now: NOW });
262
+ }
263
+ catch {
264
+ threw = true;
265
+ }
266
+ ok(!threw, '§5a shareDeck 永不抛错(stub 路径)');
267
+ // adapter 抛错 → adapter_error 收敛(注入抛错适配器)
268
+ const throwing = {
269
+ id: 'imessage',
270
+ send: async () => { throw new Error('SDK exploded'); },
271
+ };
272
+ const rThrow = await shareDeck(token, REQUEST, {
273
+ secret: SECRET,
274
+ state: granted.state,
275
+ now: NOW,
276
+ adapters: { imessage: throwing, sms: smsAdapter, slack: slackAdapter, webhook: webhookAdapter },
277
+ });
278
+ ok(!rThrow.delivered && rThrow.reason === 'adapter_error', '§5b adapter 抛错 → 收敛为 adapter_error(不外泄异常)');
279
+ // 畸形 consent state(granted 非数组)也不抛(纯函数路径稳健)
280
+ let malformedThrew = false;
281
+ try {
282
+ await shareDeck(token, REQUEST, { secret: SECRET, state: { mode: 'ask', granted: 'not-array', revoked: [] }, now: NOW });
283
+ }
284
+ catch {
285
+ malformedThrew = true;
286
+ }
287
+ ok(!malformedThrew, '§5c 畸形 consent state 不抛错(返回 ShareResult 而非异常)');
288
+ // 字节级确定性
289
+ const s2 = grantShareConsent(state, 'det-share', NOW);
290
+ const tk2 = tk('det-share');
291
+ const a = await shareDeck(tk2, REQUEST, { secret: SECRET, state: s2.state, now: NOW });
292
+ const b = await shareDeck(tk2, REQUEST, { secret: SECRET, state: s2.state, now: NOW });
293
+ ok(JSON.stringify(a) === JSON.stringify(b), '§5d 同输入两次 shareDeck 字节级一致');
294
+ // 8 类拒绝全触发
295
+ const fired = new Set();
296
+ const baseState = defaultShareConsentState();
297
+ const track = (r) => { if (!r.delivered && r.reason)
298
+ fired.add(r.reason); };
299
+ track(await shareDeck('', REQUEST, { secret: SECRET, state: baseState, now: NOW }));
300
+ track(await shareDeck(tk('x', TARGET, 1), REQUEST, { secret: SECRET, state: baseState, now: () => new Date(NOW().getTime() + 5000) }));
301
+ track(await shareDeck(tk('x2'), REQUEST, { secret: SECRET, state: baseState, now: NOW }));
302
+ track(await shareDeck(tk('x2'), REQUEST, { secret: SECRET, state: setShareConsentMode(baseState, 'off'), now: NOW }));
303
+ const gx3 = grantShareConsent(baseState, 'x3', NOW);
304
+ track(await shareDeck(tk('x3'), REQUEST, { secret: SECRET, state: revokeShareConsent(gx3.state, gx3.card.card_id), now: NOW }));
305
+ const gx4 = grantShareConsent(baseState, 'x4', NOW);
306
+ track(await shareDeck(tk('x4', { channel: 'nonsense', address: 'x' }), { target: { channel: 'nonsense', address: 'x' }, body: {} }, { secret: SECRET, state: gx4.state, now: NOW }));
307
+ const gx5 = grantShareConsent(baseState, 'x5', NOW);
308
+ track(await shareDeck(tk('x5'), REQUEST, { secret: SECRET, state: gx5.state, now: NOW }));
309
+ track(await shareDeck(tk('deck-export-2027-thailand'), REQUEST, {
310
+ secret: SECRET, state: granted.state, now: NOW,
311
+ adapters: { imessage: throwing, sms: smsAdapter, slack: slackAdapter, webhook: webhookAdapter },
312
+ }));
313
+ const expectedReasons = new Set(['token_invalid', 'token_expired', 'consent_required', 'share_consent_off', 'consent_revoked', 'unknown_channel', 'not_activated', 'adapter_error']);
314
+ ok(expectedReasons.size === fired.size && [...expectedReasons].every(r => fired.has(r)), `§5e 8 类拒绝全部真实触发(实际触发 ${[...fired].sort().join(' / ')})`);
315
+ // token 签发确定性:重签同 payload+secret 得到逐字节相同 token(非空泛正则)
316
+ const detTok = signShareToken({ share_id: 'det2', created_at: NOW().toISOString(), ttl_seconds: 60, target: TARGET }, SECRET);
317
+ ok(detTok === signShareToken({ share_id: 'det2', created_at: NOW().toISOString(), ttl_seconds: 60, target: TARGET }, SECRET), '§5f 重签同 payload+secret → token 逐字节一致(真确定性)');
318
+ // secret 解析:env / dev 默认 / production fail-closed
319
+ ok(resolveShareSecret({ SHARE_HMAC_SECRET: 'env-secret' }) === 'env-secret', '§5g 有 env → env 值');
320
+ ok(resolveShareSecret({}) === DEFAULT_DEV_SHARE_SECRET, '§5h 无 env(非 production)→ dev 默认');
321
+ let prodThrew = false;
322
+ try {
323
+ resolveShareSecret({ NODE_ENV: 'production' });
324
+ }
325
+ catch {
326
+ prodThrew = true;
327
+ }
328
+ ok(prodThrew, '§5i production 缺 SHARE_HMAC_SECRET → fail-closed 抛错(绝不静默用公开 dev 常量)');
329
+ }
330
+ // ===========================================================================
331
+ // §6 校验面运行期边界 — 失效时钟(NaN/抛错)不得让任何 token 通过;
332
+ // production 缺 secret 的配置面在隔离子进程验证(verify 收敛 token_invalid,
333
+ // resolve/sign 的刻意抛错保留在签发面)
334
+ // ===========================================================================
335
+ const TS_ROOT = join(import.meta.dirname, '..');
336
+ /** tsx CLI 文件(直接以 process.execPath 启动,不经 npx/shell 孙进程)。 */
337
+ function resolveTsxCli() {
338
+ const req = createRequire(join(TS_ROOT, 'package.json'));
339
+ const pkgPath = req.resolve('tsx/package.json');
340
+ const binField = JSON.parse(readFileSync(pkgPath, 'utf-8')).bin;
341
+ const bin = typeof binField === 'string' ? binField : binField?.tsx;
342
+ if (!bin)
343
+ throw new Error('tsx package.json 必须有 bin 入口');
344
+ return join(dirname(pkgPath), bin);
345
+ }
346
+ /** production 缺 secret 的配置面必须放隔离子进程验证:env 是进程级全局,
347
+ * 进程内改写会污染本套件其余默认 secret 断言(§5g/§5h 依赖真实 process.env)。 */
348
+ function runProdEnvChild(fixtureBody, token) {
349
+ const home = mkdtempSync(join(tmpdir(), 'gotry-share-prod-env-'));
350
+ const fixture = join(home, 'prod-env-probe.mts');
351
+ writeFileSync(fixture, fixtureBody, 'utf8');
352
+ const env = { ...process.env, NODE_ENV: 'production' };
353
+ delete env.SHARE_HMAC_SECRET;
354
+ const res = spawnSync(process.execPath, [resolveTsxCli(), fixture, token], {
355
+ cwd: TS_ROOT, encoding: 'utf8', timeout: 30_000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024, env,
356
+ });
357
+ rmSync(home, { recursive: true, force: true });
358
+ return { status: res.status, stdout: res.stdout, stderr: res.stderr };
359
+ }
360
+ {
361
+ const nanClock = () => new Date(Number.NaN);
362
+ // 过期 token + NaN 时钟:`NaN > expiresMs` 恒 false,曾把过期 token 静默判成有效(#573 缺口)
363
+ const expired = tk('nan-clock-expired', TARGET, 1);
364
+ const r1 = verifyShareToken(expired, SECRET, nanClock);
365
+ ok(!r1.ok && r1.reason === 'token_invalid', '§6a 过期 token + NaN 时钟 → token_invalid(失效时钟不得把过期判成有效)');
366
+ // 未过期 token + NaN 时钟同样拒绝:任何接受都不得建立在无效时间上
367
+ const live = tk('nan-clock-live');
368
+ const r2 = verifyShareToken(live, SECRET, nanClock);
369
+ ok(!r2.ok && r2.reason === 'token_invalid', '§6b 未过期 token + NaN 时钟 → token_invalid');
370
+ // 时钟回调抛错:异常不外泄,收敛为 token_invalid(verify 面永不抛错的字面真)
371
+ const throwingClock = () => { throw new Error('clock unavailable'); };
372
+ let threw = false;
373
+ let r3;
374
+ try {
375
+ r3 = verifyShareToken(live, SECRET, throwingClock);
376
+ }
377
+ catch {
378
+ threw = true;
379
+ }
380
+ ok(!threw && r3 !== undefined && !r3.ok && r3.reason === 'token_invalid', '§6c 时钟回调抛错 → 不外泄,收敛 token_invalid');
381
+ // production 缺 secret(NODE_ENV=production 且无 SHARE_HMAC_SECRET),全在子进程:
382
+ // token 用公开 dev 常量签——若 verify 静默回落 dev 常量,它将 verify 通过(锋利形式)
383
+ const devSigned = signShareToken({ share_id: 'prod-env-probe', created_at: NOW().toISOString(), ttl_seconds: 3600, target: TARGET }, DEFAULT_DEV_SHARE_SECRET);
384
+ const shareTokenUrl = pathToFileURL(join(TS_ROOT, 'src', 'share', 'share-token.ts')).href;
385
+ const child = runProdEnvChild([
386
+ `import { DEFAULT_DEV_SHARE_SECRET, resolveShareSecret, signShareToken, verifyShareToken } from ${JSON.stringify(shareTokenUrl)}`,
387
+ `const token = process.argv[2] ?? ''`,
388
+ `const now = () => new Date('2026-09-23T12:00:00.000Z')`,
389
+ `const out = {}`,
390
+ `try { out.defaultSecretVerify = verifyShareToken(token, undefined, now) } catch (e) { out.defaultSecretVerifyThrew = String(e) }`,
391
+ `try { out.explicitSecretVerify = verifyShareToken(token, DEFAULT_DEV_SHARE_SECRET, now) } catch (e) { out.explicitSecretVerifyThrew = String(e) }`,
392
+ `try { resolveShareSecret(); out.resolveThrew = false } catch { out.resolveThrew = true }`,
393
+ `try { signShareToken({ share_id: 'x', created_at: '2026-09-23T12:00:00.000Z', ttl_seconds: 60, target: { channel: 'imessage', address: 'a' } }); out.signThrew = false } catch { out.signThrew = true }`,
394
+ `console.log(JSON.stringify(out))`,
395
+ ].join('\n'), devSigned);
396
+ ok(child.status === 0, `§6d 子进程探针正常退出(status=${child.status};stderr=${child.stderr.slice(0, 300)})`);
397
+ let report = {};
398
+ try {
399
+ report = JSON.parse(child.stdout.trim());
400
+ }
401
+ catch { /* 失败落进下面的断言 */ }
402
+ const v = report.defaultSecretVerify;
403
+ ok(v?.ok === false && v?.reason === 'token_invalid' && report.defaultSecretVerifyThrew === undefined, '§6e production 缺 secret:verify 缺省 secret 解析 → 不抛错,收敛 token_invalid(绝不静默回落公开 dev 常量)');
404
+ const explicit = report.explicitSecretVerify;
405
+ ok(explicit?.ok === true && report.explicitSecretVerifyThrew === undefined, '§6f 显式传入 secret 的 verify 在 production 照常工作(守卫只针对缺省解析,不做全量否决)');
406
+ ok(report.resolveThrew === true, '§6g production 缺 secret:resolveShareSecret 仍抛错(签发面刻意的配置异常保留)');
407
+ ok(report.signThrew === true, '§6h production 缺 secret:signShareToken 缺省 secret 仍抛错(签发面 fail-closed)');
408
+ // 非字符串运行期 secret 键(畸形 JS 调用方输入:数字/对象/Symbol)——
409
+ // createHmac 曾在守卫体外对它们抛 TypeError;校验面必须收敛 token_invalid
410
+ const badKeys = [['123(数字)', 123], ['{}(对象)', {}], ['Symbol(bad)', Symbol('bad')]];
411
+ for (const [label, badKey] of badKeys) {
412
+ let keyThrew = false;
413
+ let keyResult;
414
+ try {
415
+ keyResult = verifyShareToken(live, badKey);
416
+ }
417
+ catch {
418
+ keyThrew = true;
419
+ }
420
+ ok(!keyThrew && keyResult !== undefined && !keyResult.ok && keyResult.reason === 'token_invalid', `§6i 非字符串 secret(${label})→ 不抛错,收敛 token_invalid`);
421
+ }
422
+ // 合法字符串键保留:空字符串键签/验往返照常(守卫只拒非字符串,不做全量收紧)
423
+ const emptyKeyToken = signShareToken({ share_id: 'empty-key', created_at: NOW().toISOString(), ttl_seconds: 60, target: TARGET }, '');
424
+ ok(verifyShareToken(emptyKeyToken, '', NOW).ok === true, '§6j 空字符串 secret(合法字符串键)签/验往返照常');
425
+ }
426
+ console.log(`\nSHARE TESTS: ${pass} pass, ${fail} fail`);
427
+ if (fail > 0) {
428
+ console.log('\nFAILURES:');
429
+ for (const f of failures)
430
+ console.log(' - ' + f);
431
+ process.exit(1);
432
+ }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * OpenFlights 骨架校验(M2 段 3,§7-1 骨架层):引擎候选集的通航性合法性校验。
3
+ * 数据源:data/openflights-skeleton.json(OpenFlights ODbL,枢纽间 168 对)。
4
+ * 语义:「查不到≠不可达」(骨架只含枢纽间),但「查到」= 候选集强证据。
5
+ * 运行:cd ts && npx tsx scripts/skeleton-check.ts HKG HKT
6
+ */
1
7
  import { readFile } from 'node:fs/promises';
2
8
  import { join } from 'node:path';
3
9
  let cached = null;
@@ -9,29 +15,25 @@ async function load() {
9
15
  }
10
16
  export async function checkConnectivity(a, b) {
11
17
  const skeleton = await load();
12
- const key = [
13
- a.toUpperCase(),
14
- b.toUpperCase()
15
- ].sort().join('-');
18
+ const key = [a.toUpperCase(), b.toUpperCase()].sort().join('-');
16
19
  const airlines = skeleton.pairs[key];
17
20
  if (airlines) {
18
- return {
19
- connected: true,
20
- airlines,
21
- evidence: `[骨架:openflights] ✅ ${a}↔${b} 直飞(${airlines.length}+ 航司:${airlines.join(',')})`
22
- };
21
+ return { connected: true, airlines, evidence: `[骨架:openflights] ✅ ${a}↔${b} 直飞(${airlines.length}+ 航司:${airlines.join(',')})` };
23
22
  }
24
- const inHub = skeleton.meta.hub_filter.includes(a.toUpperCase()) && skeleton.meta.hub_filter.includes(b.toUpperCase());
23
+ const inHub = skeleton.meta.hub_filter.includes(a.toUpperCase())
24
+ && skeleton.meta.hub_filter.includes(b.toUpperCase());
25
25
  return {
26
26
  connected: false,
27
- evidence: inHub ? `[骨架:openflights] ❌ ${a}↔${b} 枢纽间无直飞记录——引擎应将此候选降权或要求中转` : `[骨架:openflights] ○ ${a}或${b}不在枢纽集,骨架不覆盖(不作否定结论)`
27
+ evidence: inHub
28
+ ? `[骨架:openflights] ❌ ${a}↔${b} 枢纽对未在骨架覆盖(实时源以新航线为准;骨架可能滞后——请用户根据行程复核)`
29
+ : `[骨架:openflights] ○ ${a}或${b}不在枢纽集,骨架不覆盖(不作否定结论)`,
28
30
  };
29
31
  }
32
+ // CLI 直跑
33
+ // 仅作为直接 CLI 入口时执行(被 import 时不触发:此前 dsh 子进程的
34
+ // argv[2]/[3](--profile headless)恰好真值,每次启动白跑一次探测还污染 stdout)
30
35
  import { pathToFileURL } from 'node:url';
31
36
  const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
32
37
  if (isMain && process.argv[2] && process.argv[3]) {
33
38
  console.log((await checkConnectivity(process.argv[2], process.argv[3])).evidence);
34
39
  }
35
-
36
-
37
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/skeleton-check.ts
@@ -1,22 +1,39 @@
1
+ /**
2
+ * 骨架层集成验证(§7-1 消费面):开 skeletonHub + 带 route 的段 → 结果带三值标注;
3
+ * 枢纽间否定只标注不排除——EK329(数据集滞后的新航线)必须存活。
4
+ * 用户面(issue #559 卡点 4):unscaffolded leg 必须挂 ⚠ 前缀,避免用户读到「正常选择」后才在尾部 skeleton_notes 发现矛盾。
5
+ * 内核冻结面(#234)不可擅动,⚠ 标记由 loop.ts:renderSolve 从 skeleton_notes 字符串编码里推断。
6
+ * 运行(在 ts/ 下):npx tsx scripts/skeleton-integration-test.ts
7
+ */
1
8
  import assert from 'node:assert/strict';
2
9
  import { readFile } from 'node:fs/promises';
3
10
  import { join } from 'node:path';
4
11
  import { parseFlightPackToSpec, solveUnified } from '../src/unified.js';
12
+ import { renderSolve, newState } from '../src/loop.js';
5
13
  const pack = JSON.parse(await readFile(join('..', 'data', 'flights_2026.json'), 'utf-8'));
6
14
  const spec = parseFlightPackToSpec(pack);
7
15
  spec.budgetCny = 9000;
8
16
  spec.skeletonHub = true;
9
- spec.segments.forEach((s)=>{
10
- if (s.id === 'f5') s.route = 'SZX->DXB';
11
- if (s.id === 'f1') s.route = 'HKG->HKT';
12
- });
17
+ spec.segments.forEach(s => { if (s.id === 'f5')
18
+ s.route = 'SZX->DXB'; if (s.id === 'f1')
19
+ s.route = 'HKG->HKT'; });
13
20
  const r = await solveUnified(spec);
14
21
  assert.equal(r.feasible, true, '骨架否定不排除——EK329 必须仍在');
15
- const f5 = r.legs.find((l)=>l.leg === 'f5');
22
+ const f5 = r.legs.find(l => l.leg === 'f5');
16
23
  assert.equal(f5?.service, 'EK329', 'EK329 存活(骨架滞后容错)');
17
- assert.ok((r.skeleton_notes ?? []).some((n)=>n.includes('SZX') && n.includes('❌')), `枢纽否定标注在: ${r.skeleton_notes}`);
18
- assert.ok((r.skeleton_notes ?? []).some((n)=>n.includes('HKG') && n.includes('✅')), '正向标注在');
19
- console.log(`骨架集成 OK:EK329 在枢纽否定下存活,标注=[${r.skeleton_notes?.join(' | ')}]`);
20
-
21
-
22
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/skeleton-integration-test.ts
24
+ // §7-1 issue #559 卡点 4:骨架证据不再承诺「降权或中转」——承认骨架滞后,让用户据行程复核
25
+ assert.ok((r.skeleton_notes ?? []).some(n => n.includes('SZX') && n.includes('❌')), `枢纽否定标注在: ${r.skeleton_notes}`);
26
+ assert.ok((r.skeleton_notes ?? []).some(n => n.includes('HKG') && n.includes('✅')), '正向标注在');
27
+ assert.ok(!(r.skeleton_notes ?? []).some(n => n.includes('应将此候选降权') || n.includes('要求中转')), '骨架证据不再承诺「降权或中转」——承认骨架滞后,让用户据行程复核');
28
+ // §7-1 issue #559 卡点 4:renderSolve 从 skeleton_notes 推断 unscaffolded leg,
29
+ // 给 ⚠ 前缀 + 补充说明,用户读到即知(不动 #234 内核冻结面)
30
+ const state = newState();
31
+ state.solve = r;
32
+ const rendered = renderSolve(state);
33
+ const f5Line = rendered.split('\n').find(line => line.includes('f5') && line.includes('EK329')) ?? '';
34
+ assert.ok(f5Line.startsWith('- ⚠'), `f5 EK329 用户面必须有 ⚠ 前缀(实测行=${f5Line})`);
35
+ assert.ok(f5Line.includes('枢纽对未在骨架覆盖'), 'f5 用户面行内必须说明「枢纽对未在骨架覆盖」');
36
+ const f1Line = rendered.split('\n').find(line => line.includes('f1 ') && line.includes(':') && line.includes('起飞') && !line.startsWith('- ⚠')) ?? '';
37
+ assert.ok(f1Line.length > 0, 'f1 HKG↔HKT 正向覆盖的 leg 行必须在(实测查找结果为空)');
38
+ assert.ok(!f1Line.startsWith('- ⚠'), `f1 正向覆盖不应有 ⚠ 前缀(实测行=${f1Line})`);
39
+ console.log(`骨架集成 OK:EK329 仍存活,用户面 ⚠ 前缀落地;EK329 行=[${f5Line.trim()}]`);