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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,3 +1,25 @@
1
+ /**
2
+ * 12306 火车站点适配器(2026-09-03,继 ctrip-hotel 后会话面第三通道):
3
+ * - entry:https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=<城市名,电报码>&ts=<...>&date=YYYY-MM-DD&flag=N,N,Y
4
+ * (电报码表官方站表全量校准 2026-09-03,129 城;曾纠出南宁 NIZ→NNZ 错码)
5
+ * - networkHints:kyfw leftTicket/query(负载均衡变体 queryG/queryZ/queryA 等全部命中)
6
+ * - 解析:leftTicket/query 返回 data.result[] 管道行(12306 经典 | 拼接格式)。
7
+ * **第一方校准(2026-09-03)**:字段索引与站名映射(data.map[电报码])逐一
8
+ * 取自官方前端 queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版
9
+ * 公开常识索引与官方现行映射不符(站名/座位桶全错),真会话前即纠;行级
10
+ * 签名校验(车次码/时刻形态)继续兜住未来漂移:失配整行跳过,fail-visible
11
+ *
12
+ * 与机/酒通道的两点刻意差异:
13
+ * - 无登录闸:12306 余票查询是公开面(登录只关系下单),无账号数据过手;
14
+ * 扩展照样只读被动嗅探(零写行为),证据链标注「公开查询面」
15
+ * - 列表接口不含票价:如实只呈现 车次/时刻/历时/余票,票价以 12306 落地页为准
16
+ */
17
+ /** 城市电报码表(**官方口径校准 2026-09-03**:全部条目逐一核对自
18
+ * kyfw.12306.cn/otn/resources/js/framework/station_name.js 官方全量站表
19
+ * ——曾借此抓出南宁 NIZ→NNZ 错码;代表站规则=城市同名站,fs/ts 用城市码
20
+ * 服务端扩到市内全部站。词表外 unresolved 逐字保留,调用方可显式传电报码
21
+ * 覆盖——kyfw 查询页 URL 里的 fs=城市,XXX 三位码;核对快照见
22
+ * data/stations-12306-verify.json) */
1
23
  export const STATION_TELECODES = {
2
24
  "三亚": 'SEQ',
3
25
  "上海": 'SHH',
@@ -127,125 +149,138 @@ export const STATION_TELECODES = {
127
149
  "香格里拉": 'EUM',
128
150
  "香港": 'XJA',
129
151
  "齐齐哈尔": 'QHX',
130
- "龙岩": 'LYS'
152
+ "龙岩": 'LYS',
131
153
  };
132
- export function buildTrainEntryUrl(q) {
154
+ export function resolveTrainQueryTelecodes(q) {
133
155
  const unresolved = [];
134
156
  const fromTc = (q.fromStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.from.trim()];
135
157
  const toTc = (q.toStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.to.trim()];
136
- if (!fromTc) unresolved.push(q.from);
137
- if (!toTc) unresolved.push(q.to);
138
- if (unresolved.length > 0 || !/^\d{4}-\d{2}-\d{2}$/.test(q.date)) {
139
- return {
140
- ok: false,
141
- unresolved: unresolved.length > 0 ? unresolved : [
142
- q.date
143
- ]
144
- };
158
+ if (!fromTc)
159
+ unresolved.push(q.from);
160
+ if (!toTc)
161
+ unresolved.push(q.to);
162
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(q.date))
163
+ unresolved.push(q.date);
164
+ if (unresolved.length > 0 || !fromTc || !toTc)
165
+ return { ok: false, unresolved };
166
+ return { ok: true, telecodes: { fromStationTelecode: fromTc, toStationTelecode: toTc, date: q.date } };
167
+ }
168
+ /** Bind a parsed body to the exact 12306 query request that this invocation built. */
169
+ export function validateTrainQueryResponseUrl(observedUrl, expected) {
170
+ let parsed;
171
+ try {
172
+ parsed = new URL(observedUrl);
173
+ }
174
+ catch {
175
+ return { ok: false, reason: 'response URL malformed' };
176
+ }
177
+ if (parsed.protocol !== 'https:' || parsed.hostname.toLowerCase() !== TRAIN_SITE_HOST || parsed.port) {
178
+ return { ok: false, reason: 'response URL host/protocol is not the 12306 HTTPS host' };
179
+ }
180
+ if (!/^\/otn\/leftTicket\/query[A-Za-z0-9_-]*$/i.test(parsed.pathname)
181
+ || !TRAIN_NETWORK_HINTS.some((hint) => hint.test(parsed.pathname))) {
182
+ return { ok: false, reason: 'response URL is not an allowed leftTicket query endpoint' };
145
183
  }
184
+ const exactParam = (name) => {
185
+ const values = parsed.searchParams.getAll(name);
186
+ return values.length === 1 ? values[0] : undefined;
187
+ };
188
+ if (exactParam('leftTicketDTO.train_date') !== expected.date
189
+ || exactParam('leftTicketDTO.from_station') !== expected.fromStationTelecode
190
+ || exactParam('leftTicketDTO.to_station') !== expected.toStationTelecode) {
191
+ return { ok: false, reason: 'response URL query route/date does not match the invocation' };
192
+ }
193
+ return { ok: true, binding: { url: observedUrl, ...expected } };
194
+ }
195
+ export function buildTrainEntryUrl(q) {
196
+ const resolved = resolveTrainQueryTelecodes(q);
197
+ if (!resolved.ok)
198
+ return { ok: false, unresolved: resolved.unresolved };
199
+ const { fromStationTelecode: fromTc, toStationTelecode: toTc } = resolved.telecodes;
146
200
  const fs = encodeURIComponent(`${q.from.trim()},${fromTc}`);
147
201
  const ts = encodeURIComponent(`${q.to.trim()},${toTc}`);
148
- return {
149
- ok: true,
150
- url: `https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=${fs}&ts=${ts}&date=${q.date}&flag=N,N,Y`
151
- };
202
+ return { ok: true, url: `https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=${fs}&ts=${ts}&date=${q.date}&flag=N,N,Y` };
152
203
  }
153
- export const TRAIN_NETWORK_HINTS = [
154
- /leftTicket\/query/i
155
- ];
204
+ /** networkHints:余票查询 XHR(queryG/Z/A/U 等负载均衡变体全命中) */
205
+ export const TRAIN_NETWORK_HINTS = [/leftTicket\/query/i];
206
+ /** 12306 页域(content_scripts 注入面与 background 白名单的对账源) */
156
207
  export const TRAIN_SITE_HOST = 'kyfw.12306.cn';
208
+ /** 座位桶索引表(**第一方校准 2026-09-03**:逐字段取自 12306 官方前端
209
+ * queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版公开常识索引
210
+ * (19-28 段)与官方现行映射不符,首查前即纠);label=页面席别口径,未知席别
211
+ * 保留上游字段 id 原样 */
157
212
  const SEAT_BUCKETS = [
158
- {
159
- index: 20,
160
- field: 'gg_num',
161
- label: '其他(通勤)'
162
- },
163
- {
164
- index: 21,
165
- field: 'gr_num',
166
- label: '高级软卧'
167
- },
168
- {
169
- index: 22,
170
- field: 'qt_num',
171
- label: '其他'
172
- },
173
- {
174
- index: 23,
175
- field: 'rw_num',
176
- label: '软卧(一等卧)'
177
- },
178
- {
179
- index: 24,
180
- field: 'rz_num',
181
- label: '软座'
182
- },
183
- {
184
- index: 25,
185
- field: 'tz_num',
186
- label: '特等座'
187
- },
188
- {
189
- index: 26,
190
- field: 'wz_num',
191
- label: '无座'
192
- },
193
- {
194
- index: 27,
195
- field: 'yb_num',
196
- label: 'yp_b(上游席别)'
197
- },
198
- {
199
- index: 28,
200
- field: 'yw_num',
201
- label: '硬卧(二等卧)'
202
- },
203
- {
204
- index: 29,
205
- field: 'yz_num',
206
- label: '硬座'
207
- },
208
- {
209
- index: 30,
210
- field: 'ze_num',
211
- label: '二等座'
212
- },
213
- {
214
- index: 31,
215
- field: 'zy_num',
216
- label: '一等座'
217
- },
218
- {
219
- index: 32,
220
- field: 'swz_num',
221
- label: '商务座'
222
- },
223
- {
224
- index: 33,
225
- field: 'srrb_num',
226
- label: '动卧'
227
- }
213
+ { index: 20, field: 'gg_num', label: '其他(通勤)' },
214
+ { index: 21, field: 'gr_num', label: '高级软卧' },
215
+ { index: 22, field: 'qt_num', label: '其他' },
216
+ { index: 23, field: 'rw_num', label: '软卧(一等卧)' },
217
+ { index: 24, field: 'rz_num', label: '软座' },
218
+ { index: 25, field: 'tz_num', label: '特等座' },
219
+ { index: 26, field: 'wz_num', label: '无座' },
220
+ { index: 27, field: 'yb_num', label: 'yp_b(上游席别)' },
221
+ { index: 28, field: 'yw_num', label: '硬卧(二等卧)' },
222
+ { index: 29, field: 'yz_num', label: '硬座' },
223
+ { index: 30, field: 'ze_num', label: '二等座' },
224
+ { index: 31, field: 'zy_num', label: '一等座' },
225
+ { index: 32, field: 'swz_num', label: '商务座' },
226
+ { index: 33, field: 'srrb_num', label: '动卧' },
228
227
  ];
229
228
  const TRAIN_CODE_RE = /^[GDCZTKYLSF]\d{1,5}[A-Z]?$/i;
230
229
  const HHMM_RE = /^\d{2}:\d{2}$/;
230
+ export const TRAIN_QUERY_PARSE_SCHEMA = 'gotry_session_train_parse.v1';
231
+ function isRecord(value) {
232
+ return !!value && typeof value === 'object' && !Array.isArray(value);
233
+ }
234
+ /** 历时 "07:52" → 分钟 */
231
235
  function lishiToMin(s) {
232
236
  const m = /^(\d{1,3}):(\d{2})$/.exec((s ?? '').trim());
233
- if (!m) return 0;
237
+ if (!m)
238
+ return 0;
239
+ if (Number(m[2]) > 59)
240
+ return 0;
234
241
  return Number(m[1]) * 60 + Number(m[2]);
235
242
  }
243
+ function normalizeStartTrainDate(value) {
244
+ if (!/^\d{8}$/.test(value))
245
+ return undefined;
246
+ const normalized = `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
247
+ const parsed = new Date(`${normalized}T00:00:00.000Z`);
248
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== normalized)
249
+ return undefined;
250
+ return normalized;
251
+ }
252
+ /** 行级签名:车次码形态 + 时刻形态不符即整行跳过(索引漂移 fail-visible,不造数) */
236
253
  function rowToOption(row, stationMap, entryUrl) {
237
254
  const f = row.split('|');
238
- if (f.length < 34) return null;
255
+ if (f.length < 34)
256
+ return null;
239
257
  const trainCode = (f[3] ?? '').trim();
240
258
  const depTime = (f[8] ?? '').trim();
241
259
  const arrTime = (f[9] ?? '').trim();
242
- if (!TRAIN_CODE_RE.test(trainCode) || !HHMM_RE.test(depTime) || !HHMM_RE.test(arrTime)) return null;
243
- const fromStation = stationMap?.[f[6] ?? ''] ?? f[6] ?? '';
244
- const toStation = stationMap?.[f[7] ?? ''] ?? f[7] ?? '';
260
+ const durationMin = lishiToMin(f[10]);
261
+ const startTrainDateRaw = (f[13] ?? '').trim();
262
+ const startTrainDate = normalizeStartTrainDate(startTrainDateRaw);
263
+ const validHm = (value) => HHMM_RE.test(value)
264
+ && Number(value.slice(0, 2)) <= 23 && Number(value.slice(3, 5)) <= 59;
265
+ if (!TRAIN_CODE_RE.test(trainCode) || !validHm(depTime) || !validHm(arrTime)
266
+ || durationMin <= 0 || !startTrainDate || !(f[6] ?? '').trim() || !(f[7] ?? '').trim())
267
+ return null;
268
+ // 官方 cN 口径:站名 = data.map[电报码](map 缺失/缺键时退电报码原样,不猜名)
269
+ const stationName = (code) => {
270
+ if (!stationMap || !Object.prototype.hasOwnProperty.call(stationMap, code))
271
+ return code;
272
+ const mapped = stationMap[code];
273
+ return typeof mapped === 'string' && mapped.trim() ? mapped.trim() : null;
274
+ };
275
+ const fromStation = stationName((f[6] ?? '').trim());
276
+ const toStation = stationName((f[7] ?? '').trim());
277
+ if (!fromStation || !toStation)
278
+ return null;
245
279
  const seats = {};
246
- for (const b of SEAT_BUCKETS){
280
+ for (const b of SEAT_BUCKETS) {
247
281
  const v = (f[b.index] ?? '').trim();
248
- if (v && v !== '--') seats[b.label] = v;
282
+ if (v && v !== '--')
283
+ seats[b.label] = v;
249
284
  }
250
285
  return {
251
286
  trainCode,
@@ -253,33 +288,55 @@ function rowToOption(row, stationMap, entryUrl) {
253
288
  toStation,
254
289
  depTime,
255
290
  arrTime,
256
- durationMin: lishiToMin(f[10]),
291
+ durationMin,
292
+ startTrainDate,
257
293
  canWebBuy: (f[11] ?? '').trim(),
258
294
  seats,
259
- jumpUrl: entryUrl
295
+ jumpUrl: entryUrl,
260
296
  };
261
297
  }
262
- export function parseLeftTicketQuery(body, entryUrl, opts = {}) {
298
+ /** 解析 leftTicket/query 响应的 typed outcome。
299
+ * recognized-empty 只代表明确存在的 data.result=[];
300
+ * recognized-nonempty 要求批次每一行都通过签名校验,任一畸形行即整个批次
301
+ * malformed,不暴露部分候选。transport/runtime failure 不在此纯解析函数伪造。
302
+ */
303
+ export function parseLeftTicketQueryResult(body, entryUrl, opts = {}) {
263
304
  let raw;
264
305
  try {
265
306
  raw = JSON.parse(body);
266
- } catch {
267
- return [];
268
307
  }
269
- const rows = raw.data?.result;
270
- if (!Array.isArray(rows)) return [];
271
- const map = raw.data?.map && typeof raw.data.map === 'object' ? raw.data.map : undefined;
308
+ catch {
309
+ return { kind: 'malformed', reason: 'response body is not valid JSON' };
310
+ }
311
+ if (!isRecord(raw) || !isRecord(raw.data))
312
+ return { kind: 'malformed', reason: 'response root/data is not an object' };
313
+ const rows = raw.data.result;
314
+ if (!Array.isArray(rows))
315
+ return { kind: 'malformed', reason: 'data.result is not an array' };
316
+ if (raw.data.map !== undefined && !isRecord(raw.data.map)) {
317
+ return { kind: 'malformed', reason: 'data.map is not an object' };
318
+ }
319
+ if (rows.length === 0)
320
+ return { kind: 'recognized-empty', trains: [] };
321
+ const map = raw.data.map;
272
322
  const out = [];
273
- for (const r of rows){
274
- if (typeof r !== 'string') continue;
323
+ for (const r of rows) {
324
+ if (typeof r !== 'string')
325
+ return { kind: 'malformed', reason: 'data.result contains a non-string row' };
275
326
  const opt = rowToOption(r, map, entryUrl);
276
- if (opt) {
327
+ if (!opt)
328
+ return { kind: 'malformed', reason: 'data.result contains a malformed or unrecognized row' };
329
+ if (out.length < (opts.maxItems ?? 30))
277
330
  out.push(opt);
278
- if (out.length >= (opts.maxItems ?? 30)) break;
279
- }
280
331
  }
281
- return out;
332
+ return { kind: 'recognized-nonempty', trains: out };
333
+ }
334
+ /** 兼容旧调用方:typed outcome 之外仍保留旧的数组投影,错误与 genuine empty 均为 []。 */
335
+ export function parseLeftTicketQuery(body, entryUrl, opts = {}) {
336
+ const outcome = parseLeftTicketQueryResult(body, entryUrl, opts);
337
+ return outcome.kind === 'recognized-nonempty' ? outcome.trains : [];
338
+ }
339
+ /** Positive availability is deliberately a small closed set, not canWebBuy alone. */
340
+ export function hasRecognizedAvailableSeat(option) {
341
+ return Object.values(option.seats).some(value => value === '有' || /^[1-9]\d*$/.test(value));
282
342
  }
283
-
284
-
285
- //# sourceURL=ts/capabilities/session/adapters/rail-12306.ts
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Session 双源字段合同与 fixture 评分器。
3
+ *
4
+ * 只处理已经脱敏、结构化的证据;不打开浏览器、不读登录态,也不执行任何写操作。
5
+ */
1
6
  export const SESSION_BENCHMARK_SCHEMA_VERSION = 'session-double-source.v1';
2
7
  export const SESSION_FIELD_ACCURACY_THRESHOLD = 0.9;
3
8
  export const REQUIRED_COMPARABLE_FIELDS = [
@@ -11,108 +16,64 @@ export const REQUIRED_COMPARABLE_FIELDS = [
11
16
  'price',
12
17
  'source',
13
18
  'fetched_at',
14
- 'verdict'
19
+ 'verdict',
15
20
  ];
16
21
  function isMissing(value) {
17
- return value === undefined || value === null || typeof value === 'string' && value.trim() === '';
22
+ return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');
18
23
  }
19
24
  function sameValue(left, right) {
20
- if (typeof left !== typeof right) return false;
21
- return typeof left === 'number' && typeof right === 'number' ? Object.is(left, right) : left === right;
25
+ if (typeof left !== typeof right)
26
+ return false;
27
+ return typeof left === 'number' && typeof right === 'number'
28
+ ? Object.is(left, right)
29
+ : left === right;
22
30
  }
23
31
  function sameComparison(row) {
24
- if (/\.(?:departure_at|arrival_at)$/.test(row.path) && typeof row.expected === 'string' && typeof row.actual === 'string') {
32
+ if (/\.(?:departure_at|arrival_at)$/.test(row.path)
33
+ && typeof row.expected === 'string'
34
+ && typeof row.actual === 'string') {
25
35
  const expectedAt = Date.parse(row.expected);
26
36
  const actualAt = Date.parse(row.actual);
27
- if (Number.isFinite(expectedAt) && Number.isFinite(actualAt)) return expectedAt === actualAt;
37
+ if (Number.isFinite(expectedAt) && Number.isFinite(actualAt))
38
+ return expectedAt === actualAt;
28
39
  }
29
40
  return sameValue(row.expected, row.actual);
30
41
  }
31
42
  function fixtureComparisons(expected, actual) {
32
43
  const rows = [
33
- {
34
- path: 'query_id',
35
- expected: expected.query_id,
36
- actual: actual.query_id
37
- },
38
- {
39
- path: 'route_segments.length',
40
- expected: expected.route_segments.length,
41
- actual: actual.route_segments?.length
42
- },
43
- {
44
- path: 'journey_type',
45
- expected: expected.journey_type,
46
- actual: actual.journey_type
47
- },
48
- {
49
- path: 'currency',
50
- expected: expected.currency,
51
- actual: actual.currency
52
- },
53
- {
54
- path: 'price',
55
- expected: expected.price,
56
- actual: actual.price
57
- },
58
- {
59
- path: 'source',
60
- expected: expected.source,
61
- actual: actual.source
62
- },
63
- {
64
- path: 'fetched_at',
65
- expected: expected.fetched_at,
66
- actual: actual.fetched_at
67
- },
68
- {
69
- path: 'verdict',
70
- expected: expected.verdict,
71
- actual: actual.verdict
72
- }
44
+ { path: 'query_id', expected: expected.query_id, actual: actual.query_id },
45
+ { path: 'route_segments.length', expected: expected.route_segments.length, actual: actual.route_segments?.length },
46
+ { path: 'journey_type', expected: expected.journey_type, actual: actual.journey_type },
47
+ { path: 'currency', expected: expected.currency, actual: actual.currency },
48
+ { path: 'price', expected: expected.price, actual: actual.price },
49
+ { path: 'source', expected: expected.source, actual: actual.source },
50
+ { path: 'fetched_at', expected: expected.fetched_at, actual: actual.fetched_at },
51
+ { path: 'verdict', expected: expected.verdict, actual: actual.verdict },
73
52
  ];
74
- for(let index = 0; index < expected.route_segments.length; index += 1){
53
+ for (let index = 0; index < expected.route_segments.length; index += 1) {
75
54
  const exp = expected.route_segments[index];
76
55
  const act = actual.route_segments?.[index];
77
- rows.push({
78
- path: `route_segments[${index}].from`,
79
- expected: exp.from,
80
- actual: act?.from
81
- }, {
82
- path: `route_segments[${index}].to`,
83
- expected: exp.to,
84
- actual: act?.to
85
- }, {
86
- path: `route_segments[${index}].departure_at`,
87
- expected: exp.departure_at,
88
- actual: act?.departure_at
89
- }, {
90
- path: `route_segments[${index}].arrival_at`,
91
- expected: exp.arrival_at,
92
- actual: act?.arrival_at
93
- }, {
94
- path: `route_segments[${index}].transport_number`,
95
- expected: exp.transport_number,
96
- actual: act?.transport_number
97
- });
56
+ rows.push({ path: `route_segments[${index}].from`, expected: exp.from, actual: act?.from }, { path: `route_segments[${index}].to`, expected: exp.to, actual: act?.to }, { path: `route_segments[${index}].departure_at`, expected: exp.departure_at, actual: act?.departure_at }, { path: `route_segments[${index}].arrival_at`, expected: exp.arrival_at, actual: act?.arrival_at }, { path: `route_segments[${index}].transport_number`, expected: exp.transport_number, actual: act?.transport_number });
98
57
  }
99
58
  return rows;
100
59
  }
101
60
  export function scoreSessionFixture(expected, actual, threshold = SESSION_FIELD_ACCURACY_THRESHOLD) {
102
61
  const rows = fixtureComparisons(expected, actual);
103
62
  const fixtureErrors = [
104
- ...rows.filter((row)=>isMissing(row.expected)).map((row)=>row.path),
105
- ...requiredMissing(expected)
63
+ ...rows.filter((row) => isMissing(row.expected)).map((row) => row.path),
64
+ ...requiredMissing(expected),
106
65
  ];
107
66
  const missing = [];
108
67
  const incorrect = [];
109
68
  let correct = 0;
110
- for (const row of rows){
69
+ for (const row of rows) {
111
70
  if (isMissing(row.actual)) {
112
71
  missing.push(row.path);
113
- } else if (!sameValue(row.expected, row.actual)) {
72
+ }
73
+ else if (!sameValue(row.expected, row.actual)) {
114
74
  incorrect.push(row.path);
115
- } else {
75
+ }
76
+ else {
116
77
  correct += 1;
117
78
  }
118
79
  }
@@ -126,9 +87,7 @@ export function scoreSessionFixture(expected, actual, threshold = SESSION_FIELD_
126
87
  accuracy,
127
88
  missing,
128
89
  incorrect,
129
- fixture_errors: [
130
- ...new Set(fixtureErrors)
131
- ]
90
+ fixture_errors: [...new Set(fixtureErrors)],
132
91
  };
133
92
  }
134
93
  function isIsoTimestamp(value) {
@@ -136,21 +95,34 @@ function isIsoTimestamp(value) {
136
95
  }
137
96
  function requiredMissing(record) {
138
97
  const missing = [];
139
- if (!record.query_id.trim()) missing.push('query_id');
140
- if (record.route_segments.length === 0) missing.push('route_segments');
141
- if (!record.currency.trim()) missing.push('currency');
142
- if (!(record.price > 0)) missing.push('price');
143
- if (!record.source.trim()) missing.push('source');
144
- if (!isIsoTimestamp(record.fetched_at)) missing.push('fetched_at');
145
- if (record.journey_type === 'direct' && record.route_segments.length !== 1) missing.push('journey_type/route_segments');
146
- if (record.journey_type === 'transfer' && record.route_segments.length < 2) missing.push('journey_type/route_segments');
147
- for(let index = 0; index < record.route_segments.length; index += 1){
98
+ if (!record.query_id.trim())
99
+ missing.push('query_id');
100
+ if (record.route_segments.length === 0)
101
+ missing.push('route_segments');
102
+ if (!record.currency.trim())
103
+ missing.push('currency');
104
+ if (!(record.price > 0))
105
+ missing.push('price');
106
+ if (!record.source.trim())
107
+ missing.push('source');
108
+ if (!isIsoTimestamp(record.fetched_at))
109
+ missing.push('fetched_at');
110
+ if (record.journey_type === 'direct' && record.route_segments.length !== 1)
111
+ missing.push('journey_type/route_segments');
112
+ if (record.journey_type === 'transfer' && record.route_segments.length < 2)
113
+ missing.push('journey_type/route_segments');
114
+ for (let index = 0; index < record.route_segments.length; index += 1) {
148
115
  const segment = record.route_segments[index];
149
- if (!segment.from.trim()) missing.push(`route_segments[${index}].from`);
150
- if (!segment.to.trim()) missing.push(`route_segments[${index}].to`);
151
- if (!isIsoTimestamp(segment.departure_at)) missing.push(`route_segments[${index}].departure_at`);
152
- if (!isIsoTimestamp(segment.arrival_at)) missing.push(`route_segments[${index}].arrival_at`);
153
- if (!segment.transport_number.trim()) missing.push(`route_segments[${index}].transport_number`);
116
+ if (!segment.from.trim())
117
+ missing.push(`route_segments[${index}].from`);
118
+ if (!segment.to.trim())
119
+ missing.push(`route_segments[${index}].to`);
120
+ if (!isIsoTimestamp(segment.departure_at))
121
+ missing.push(`route_segments[${index}].departure_at`);
122
+ if (!isIsoTimestamp(segment.arrival_at))
123
+ missing.push(`route_segments[${index}].arrival_at`);
124
+ if (!segment.transport_number.trim())
125
+ missing.push(`route_segments[${index}].transport_number`);
154
126
  }
155
127
  return missing;
156
128
  }
@@ -161,59 +133,21 @@ function baseEvaluation(state, quotaDisposition, extra = {}) {
161
133
  quota_disposition: quotaDisposition,
162
134
  mismatches: extra.mismatches ?? [],
163
135
  missing: extra.missing ?? [],
164
- ...extra.price_delta === undefined ? {} : {
165
- price_delta: extra.price_delta
166
- }
136
+ ...(extra.price_delta === undefined ? {} : { price_delta: extra.price_delta }),
167
137
  };
168
138
  }
169
139
  function alignmentComparisons(official, session) {
170
140
  const rows = [
171
- {
172
- path: 'query_id',
173
- expected: official.query_id,
174
- actual: session.query_id
175
- },
176
- {
177
- path: 'route_segments.length',
178
- expected: official.route_segments.length,
179
- actual: session.route_segments.length
180
- },
181
- {
182
- path: 'journey_type',
183
- expected: official.journey_type,
184
- actual: session.journey_type
185
- },
186
- {
187
- path: 'currency',
188
- expected: official.currency,
189
- actual: session.currency
190
- }
141
+ { path: 'query_id', expected: official.query_id, actual: session.query_id },
142
+ { path: 'route_segments.length', expected: official.route_segments.length, actual: session.route_segments.length },
143
+ { path: 'journey_type', expected: official.journey_type, actual: session.journey_type },
144
+ { path: 'currency', expected: official.currency, actual: session.currency },
191
145
  ];
192
146
  const comparableSegments = Math.min(official.route_segments.length, session.route_segments.length);
193
- for(let index = 0; index < comparableSegments; index += 1){
147
+ for (let index = 0; index < comparableSegments; index += 1) {
194
148
  const exp = official.route_segments[index];
195
149
  const act = session.route_segments[index];
196
- rows.push({
197
- path: `route_segments[${index}].from`,
198
- expected: exp.from,
199
- actual: act.from
200
- }, {
201
- path: `route_segments[${index}].to`,
202
- expected: exp.to,
203
- actual: act.to
204
- }, {
205
- path: `route_segments[${index}].departure_at`,
206
- expected: exp.departure_at,
207
- actual: act.departure_at
208
- }, {
209
- path: `route_segments[${index}].arrival_at`,
210
- expected: exp.arrival_at,
211
- actual: act.arrival_at
212
- }, {
213
- path: `route_segments[${index}].transport_number`,
214
- expected: exp.transport_number,
215
- actual: act.transport_number
216
- });
150
+ rows.push({ path: `route_segments[${index}].from`, expected: exp.from, actual: act.from }, { path: `route_segments[${index}].to`, expected: exp.to, actual: act.to }, { path: `route_segments[${index}].departure_at`, expected: exp.departure_at, actual: act.departure_at }, { path: `route_segments[${index}].arrival_at`, expected: exp.arrival_at, actual: act.arrival_at }, { path: `route_segments[${index}].transport_number`, expected: exp.transport_number, actual: act.transport_number });
217
151
  }
218
152
  return rows;
219
153
  }
@@ -226,28 +160,30 @@ export function evaluateDoubleSource(input) {
226
160
  if ((session?.read_guard_blocked ?? 0) !== 0 || (official?.read_guard_blocked ?? 0) !== 0) {
227
161
  return baseEvaluation('guard_violation', 'no_spend_stop');
228
162
  }
229
- if (!session) return baseEvaluation('source_unavailable', 'no_spend_stop');
230
- if (session.verdict === 'needs-attach') return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
231
- if (session.verdict === 'needs-extension') return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
232
- if (session.verdict === 'needs-login') return baseEvaluation('waiting_login', 'no_spend_waiting_user');
163
+ if (!session)
164
+ return baseEvaluation('source_unavailable', 'no_spend_stop');
165
+ if (session.verdict === 'needs-attach')
166
+ return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
167
+ // 扩展车道(2026-08-29 PRIMARY 传输)未就绪:与 waiting-* 同族 no-spend,等待用户一次性安装
168
+ if (session.verdict === 'needs-extension')
169
+ return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
170
+ if (session.verdict === 'needs-login')
171
+ return baseEvaluation('waiting_login', 'no_spend_waiting_user');
233
172
  if (!official || official.verdict !== 'hit' || session.verdict !== 'hit') {
234
173
  return baseEvaluation('source_unavailable', 'no_spend_stop');
235
174
  }
236
175
  const missing = [
237
- ...requiredMissing(official).map((path)=>`official.${path}`),
238
- ...requiredMissing(session).map((path)=>`session.${path}`)
176
+ ...requiredMissing(official).map((path) => `official.${path}`),
177
+ ...requiredMissing(session).map((path) => `session.${path}`),
239
178
  ];
240
179
  if (missing.length > 0) {
241
- return baseEvaluation('invalid_contract', 'no_spend_stop', {
242
- missing
243
- });
180
+ return baseEvaluation('invalid_contract', 'no_spend_stop', { missing });
244
181
  }
245
- const mismatches = alignmentComparisons(official, session).filter((row)=>!sameComparison(row)).map((row)=>row.path);
182
+ const mismatches = alignmentComparisons(official, session)
183
+ .filter((row) => !sameComparison(row))
184
+ .map((row) => row.path);
246
185
  return baseEvaluation(mismatches.length === 0 ? 'comparable' : 'divergent', 'evidence_ready', {
247
186
  mismatches,
248
- price_delta: session.price - official.price
187
+ price_delta: session.price - official.price,
249
188
  });
250
189
  }
251
-
252
-
253
- //# sourceURL=ts/capabilities/session/benchmark.ts