@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
@@ -0,0 +1,21 @@
1
+ import {
2
+ createSessionBridge,
3
+ EXTENSION_ORIGIN,
4
+ } from '../../capabilities/session/extension-bridge.js'
5
+
6
+ const created = await createSessionBridge({ ports: [0], extensionOrigin: EXTENSION_ORIGIN })
7
+ if (!created.ok) {
8
+ process.send?.({ kind: 'error', summary: created.summary })
9
+ process.exit(1)
10
+ }
11
+
12
+ // Parent first parks an external /jobs request, then releases this sole
13
+ // intentional event-loop reference. The default bridge must not keep the
14
+ // child alive after that point.
15
+ const hold = setInterval(() => {}, 60_000)
16
+ process.send?.({ kind: 'listening', port: created.bridge.port })
17
+ process.once('message', (message) => {
18
+ if (typeof message !== 'object' || message === null || !('kind' in message) || message.kind !== 'release') return
19
+ clearInterval(hold)
20
+ process.disconnect?.()
21
+ })
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Issue #363 — 航班/酒店锚点字段指纹(注册 `gotry_fact_gate` 路径 E2E)。
3
+ *
4
+ * 真实执行面:在临时 stateRoot 下装载 gotry-tools 插件,通过 `apply(ctx, config)`
5
+ * 装载注册定义,捕获 `gotry_fact_gate` 的 execute 函数,以 markdown 直接调用——
6
+ * 经注册的 tool definition 走完 `loadFactRegistry(stateRoot)` → `gateArtifact`
7
+ * 公共闸侧路径,完整覆盖 D-26 / D-30 残余收口。
8
+ * 不启动 dsh 宿主、不调用真实 ToolRuntime、不触碰供应商 / browser / policy
9
+ * service / 共享状态。
10
+ *
11
+ * 测试面与 §15 一致:
12
+ * - 3 个根反例 + canonical 各 1 行(共 5)——以注册闸执行;
13
+ * - §4b 价格兼容:总预算/行李费/总计/显式票价/千分位 等形态不被字段指纹吞并;
14
+ * - 价格矛盾仍走 price_contradicted,不退化为 fact_anchor_unknown;
15
+ * - 列车/政策 canonical 不被新字段指纹吞并。
16
+ *
17
+ * 运行(在 ts/ 下):
18
+ * PATH=/opt/homebrew/opt/node@24/bin:$PATH \
19
+ * npx tsx scripts/flight-hotel-anchor-363-registered-e2e.ts
20
+ */
21
+ import assert from 'node:assert/strict';
22
+ import { mkdtempSync, rmSync, mkdirSync } from 'node:fs';
23
+ import { tmpdir } from 'node:os';
24
+ import { join } from 'node:path';
25
+ import { apply } from '../src/index.js';
26
+ import { BOOKABLE_FACT_SCHEMA, factsFromFlyai, factsFromHotel, makeFactId, renderFlightFact, renderHotelFact, renderPolicyFact, } from '../src/bookable-facts.js';
27
+ import { appendFacts } from '../capabilities/fact-log.js';
28
+ const FETCHED = '2026-08-29T09:51:00.000Z';
29
+ const HOTEL_FETCHED = '2026-09-04T00:00:00.000Z';
30
+ async function main() {
31
+ const home = mkdtempSync(join(tmpdir(), 'gotry-363-flight-hotel-anchor-'));
32
+ const stateRoot = join(home, 'state');
33
+ const cwd = join(home, 'workspace');
34
+ mkdirSync(stateRoot, { recursive: true });
35
+ mkdirSync(cwd, { recursive: true });
36
+ // 与根反例 fixture 同源:UO724 HKG→HKT 2027-07-17(bookable_exact_date)。
37
+ const map = JSON.parse(await import('node:fs/promises').then(m => m.readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8')));
38
+ const flight = factsFromFlyai({ kind: 'flight', origin: '香港', destination: '普吉', date: '2027-07-17' }, {
39
+ verdict: 'hit',
40
+ options: [{
41
+ no: 'UO724', name: '香港快运航空',
42
+ depDateTime: '2027-07-17T07:55:00', arrDateTime: '2027-07-17T10:30:00',
43
+ depStation: 'HKG', arrStation: 'HKT',
44
+ price: 793, nonstop: true,
45
+ }],
46
+ }, FETCHED, map.city_alias)[0];
47
+ const hotel = factsFromHotel({
48
+ source: 'flyai-hotel', destination: '大理',
49
+ checkIn: '2026-10-01', checkOut: '2026-10-03',
50
+ verdict: 'hit', options: 9, evidence: 'synthetic', fetchedAt: HOTEL_FETCHED,
51
+ })[0];
52
+ const policy = {
53
+ schema: BOOKABLE_FACT_SCHEMA,
54
+ fact_id: makeFactId(['policy-363', 'reg', '泰国免签']),
55
+ kind: 'policy',
56
+ subject: '泰国入境(中国护照)',
57
+ statement: '免签停留(口径以泰方公告为准);UAE 居民返程应优先校验 residence visa / Emirates ID 而非游客免签口径',
58
+ source: 'web:official',
59
+ query_id: 'web:policy:泰国免签',
60
+ fetched_at: FETCHED,
61
+ as_of: '2026-08-29',
62
+ };
63
+ // 走真实事实日志路径(append-only JSONL),与产品 gotry_flyai_search /
64
+ // gotry_session_search 同款事实落账接口;闸侧 loadFactRegistry 直接读这里。
65
+ await appendFacts(stateRoot, [flight, hotel, policy]);
66
+ // 装载 gotry-tools 插件 → 捕获 gotry_fact_gate 的真实 execute。
67
+ const registered = [];
68
+ const ctx = {
69
+ tools: { register: (t) => registered.push(t) },
70
+ systemPrompt: { section: () => undefined, variable: () => undefined },
71
+ on: () => () => undefined,
72
+ };
73
+ apply(ctx, {
74
+ stateRoot,
75
+ timeoutMs: 30_000,
76
+ hbcliBin: 'hbcli-not-on-path',
77
+ sessionAccess: 'off',
78
+ });
79
+ const factGate = registered.find(t => t.name === 'gotry_fact_gate');
80
+ if (!factGate)
81
+ throw new Error(`FAIL: 注册定义列表缺少 gotry_fact_gate,实际注册: ${registered.map(t => t.name).join(',')}`);
82
+ const exec = { agent: { session: { header: { cwd } } } };
83
+ const flightCanonical = renderFlightFact(flight);
84
+ const hotelCanonical = renderHotelFact(hotel);
85
+ // Case 1:canonical flight 行经真实 gotry_fact_gate → pass
86
+ const r1 = await factGate.execute({ markdown: ['## 航班', flightCanonical].join('\n') }, exec);
87
+ assert.equal(r1.verdict, 'pass', `canonical flight 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r1.verdict, summary: r1.summary })}`);
88
+ assert.equal(r1.traceable, 1);
89
+ console.log(' ok - 注册 gotry_fact_gate canonical flight → pass');
90
+ // Case 2:canonical hotel 行经真实 gotry_fact_gate → pass
91
+ const r2 = await factGate.execute({ markdown: ['## 住宿', hotelCanonical].join('\n') }, exec);
92
+ assert.equal(r2.verdict, 'pass', `canonical hotel 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r2.verdict, summary: r2.summary })}`);
93
+ assert.equal(r2.traceable, 1);
94
+ console.log(' ok - 注册 gotry_fact_gate canonical hotel → pass');
95
+ // Case 3:flight_no 改写(UO724 → UO999),保留 anchor → blocked/fact_anchor_unknown
96
+ const tamperedFlight = flightCanonical.replace('UO724', 'UO999');
97
+ const r3 = await factGate.execute({ markdown: ['## 航班', tamperedFlight].join('\n') }, exec);
98
+ assert.equal(r3.verdict, 'blocked', `flight_no 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r3.verdict, summary: r3.summary })}`);
99
+ assert.equal(r3.traceable, 0);
100
+ assert.ok(r3.violations?.some(v => v.kind === 'fact_anchor_unknown' && /UO999|UO724/.test(JSON.stringify(v))), `flight_no 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r3.violations?.map(v => v.kind))}`);
101
+ console.log(' ok - 注册 gotry_fact_gate flight_no UO724→UO999 → blocked/fact_anchor_unknown');
102
+ // Case 4:hotel destination 改写(大理 → 丽江)→ blocked/fact_anchor_unknown
103
+ const tamperedHotelDest = hotelCanonical.replace('大理', '丽江');
104
+ const r4 = await factGate.execute({ markdown: ['## 住宿', tamperedHotelDest].join('\n') }, exec);
105
+ assert.equal(r4.verdict, 'blocked', `hotel destination 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r4.verdict, summary: r4.summary })}`);
106
+ assert.ok(r4.violations?.some(v => v.kind === 'fact_anchor_unknown'), `hotel destination 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r4.violations?.map(v => v.kind))}`);
107
+ console.log(' ok - 注册 gotry_fact_gate hotel destination 大理→丽江 → blocked/fact_anchor_unknown');
108
+ // Case 5:hotel date 改写(2026-10-01 → 2026-12-25)→ blocked/fact_anchor_unknown
109
+ const tamperedHotelDate = hotelCanonical.replace('2026-10-01', '2026-12-25');
110
+ const r5 = await factGate.execute({ markdown: ['## 住宿', tamperedHotelDate].join('\n') }, exec);
111
+ assert.equal(r5.verdict, 'blocked', `hotel date 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r5.verdict, summary: r5.summary })}`);
112
+ assert.equal(r5.traceable, 0);
113
+ assert.ok(r5.violations?.some(v => v.kind === 'fact_anchor_unknown'), `hotel date 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r5.violations?.map(v => v.kind))}`);
114
+ console.log(' ok - 注册 gotry_fact_gate hotel date 2026-10-01→2026-12-25 → blocked/fact_anchor_unknown');
115
+ // Case 6:§4b 价格兼容——尾随总预算 → 不被 fact_anchor_unknown 吞并
116
+ const appendedBudget = `${flightCanonical};总预算¥1000`;
117
+ const r6 = await factGate.execute({ markdown: ['## 航班', appendedBudget].join('\n') }, exec);
118
+ assert.equal(r6.verdict, 'pass', `尾随总预算经注册闸应 pass,实际: ${JSON.stringify({ verdict: r6.verdict, summary: r6.summary })}`);
119
+ assert.ok(!r6.violations?.some(v => v.kind === 'fact_anchor_unknown'), `尾随总预算不应有 fact_anchor_unknown,实际: ${JSON.stringify(r6.violations?.map(v => v.kind))}`);
120
+ console.log(' ok - 注册 gotry_fact_gate flight §4b 价格兼容(尾随总预算)→ pass');
121
+ // Case 7:§4b 价格兼容——证据链前裸金额(¥999)→ 走 unverified_price_claim,
122
+ // 不退化为 fact_anchor_unknown
123
+ const bareAmount = flightCanonical.replace(' [flyai@', ';¥999 [flyai@');
124
+ const r7 = await factGate.execute({ markdown: ['## 航班', bareAmount].join('\n') }, exec);
125
+ assert.equal(r7.verdict, 'blocked', `证据链前裸金额经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r7.verdict, summary: r7.summary })}`);
126
+ assert.ok(r7.violations?.some(v => v.kind === 'unverified_price_claim'), `证据链前裸金额应有 unverified_price_claim,实际: ${JSON.stringify(r7.violations?.map(v => v.kind))}`);
127
+ assert.ok(!r7.violations?.some(v => v.kind === 'fact_anchor_unknown'), `证据链前裸金额不应有 fact_anchor_unknown,实际: ${JSON.stringify(r7.violations?.map(v => v.kind))}`);
128
+ console.log(' ok - 注册 gotry_fact_gate flight §4b 价格兼容(证据链前裸金额)→ blocked/unverified_price_claim');
129
+ // Case 8:§4b 价格矛盾(¥999 替换 ¥793)→ price_contradicted 分类保留,
130
+ // 不退化为 fact_anchor_unknown
131
+ const contradicted = flightCanonical.replace('¥793', '¥999');
132
+ const r8 = await factGate.execute({ markdown: ['## 航班', contradicted].join('\n') }, exec);
133
+ assert.equal(r8.verdict, 'blocked', `价格矛盾经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r8.verdict, summary: r8.summary })}`);
134
+ assert.ok(r8.violations?.some(v => v.kind === 'price_contradicted'), `价格矛盾应有 price_contradicted,实际: ${JSON.stringify(r8.violations?.map(v => v.kind))}`);
135
+ assert.ok(!r8.violations?.some(v => v.kind === 'fact_anchor_unknown'), `价格矛盾不应有 fact_anchor_unknown,实际: ${JSON.stringify(r8.violations?.map(v => v.kind))}`);
136
+ console.log(' ok - 注册 gotry_fact_gate flight §4b 价格矛盾 → blocked/price_contradicted(分类保留)');
137
+ // Case 9:整行改写(flight_no + 价格)→ fact_anchor_unknown(字段级别 fail-closed 优先)
138
+ const allTampered = flightCanonical.replace('UO724', 'UO999').replace('¥793', '¥1000');
139
+ const r9 = await factGate.execute({ markdown: ['## 航班', allTampered].join('\n') }, exec);
140
+ assert.ok(r9.violations?.some(v => v.kind === 'fact_anchor_unknown'), `flight 字段 + 价格联合改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r9.violations?.map(v => v.kind))}`);
141
+ console.log(' ok - 注册 gotry_fact_gate flight 字段+价格联合改写 → fact_anchor_unknown');
142
+ // Case 10:借用合法 fact_id 写入乱码 flight_no → fact_anchor_unknown
143
+ const borrowedAnchor = `## 航班\n- HKG→HKT 2027-07-17 XX9999(未知) 07:55→10:30 直飞 ¥793 [flyai@${flight.fetched_at} #${flight.query_id}] <!-- fact:${flight.fact_id} -->`;
144
+ const r10 = await factGate.execute({ markdown: borrowedAnchor }, exec);
145
+ assert.ok(r10.violations?.some(v => v.kind === 'fact_anchor_unknown'), `借用合法 fact_id 写入乱码 flight_no 应有 fact_anchor_unknown,实际: ${JSON.stringify(r10.violations?.map(v => v.kind))}`);
146
+ console.log(' ok - 注册 gotry_fact_gate 借用合法 fact_id + 乱码 flight_no → fact_anchor_unknown');
147
+ // Case 11:列车 canonical 形态保留——列车 kind 不走 flight_no 字段指纹
148
+ const gTrain = factsFromFlyai({ kind: 'train', origin: '香港', destination: '普吉', date: '2027-07-17' }, { verdict: 'hit', options: [{ no: 'G1234', depDateTime: '2027-07-17T09:00:00', arrDateTime: '2027-07-17T13:00:00' }] }, FETCHED, map.city_alias)[0];
149
+ await appendFacts(stateRoot, [gTrain]);
150
+ const trainRendered = renderFlightFact(gTrain);
151
+ const r11 = await factGate.execute({ markdown: ['## 车次', trainRendered].join('\n') }, exec);
152
+ assert.equal(r11.verdict, 'pass', `train canonical 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r11.verdict, summary: r11.summary })}`);
153
+ assert.ok(!r11.violations?.some(v => v.kind === 'fact_anchor_unknown'), `train canonical 不应有 fact_anchor_unknown,实际: ${JSON.stringify(r11.violations?.map(v => v.kind))}`);
154
+ console.log(' ok - 注册 gotry_fact_gate train canonical → pass(列车/航班分类独立,不被字段指纹吞并)');
155
+ // Case 12:政策锚点形态保留——政策行不受 flight_no/destination/check_in 字段指纹影响
156
+ const policyLine = renderPolicyFact(policy);
157
+ const r12 = await factGate.execute({ markdown: ['## 政策', policyLine].join('\n') }, exec);
158
+ assert.equal(r12.verdict, 'pass', `policy canonical 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r12.verdict, summary: r12.summary })}`);
159
+ assert.ok(!r12.violations?.some(v => v.kind === 'fact_anchor_unknown'), `policy canonical 不应有 fact_anchor_unknown,实际: ${JSON.stringify(r12.violations?.map(v => v.kind))}`);
160
+ console.log(' ok - 注册 gotry_fact_gate policy canonical → pass(政策不受新字段指纹影响)');
161
+ // Case 13:借用合法 fact_id 写入伪造酒店 destination → blocked/fact_anchor_unknown
162
+ const borrowedHotel = `## 住宿\n- 丽江 2026-10-01→2026-10-03:住宿已核验(在架 9 家;价格上游打码,真实价以落地页为准) <!-- fact:${hotel.fact_id} -->`;
163
+ const r13 = await factGate.execute({ markdown: borrowedHotel }, exec);
164
+ assert.ok(r13.violations?.some(v => v.kind === 'fact_anchor_unknown'), `借用合法 hotel fact_id 写入伪造 destination 应有 fact_anchor_unknown,实际: ${JSON.stringify(r13.violations?.map(v => v.kind))}`);
165
+ console.log(' ok - 注册 gotry_fact_gate 借用合法 hotel fact_id + 伪造 destination → fact_anchor_unknown');
166
+ rmSync(home, { recursive: true, force: true });
167
+ console.log('\nFLIGHT/HOTEL ANCHOR #363 REGISTERED E2E: pass (deterministic synthetic, isolated stateRoot, registered gotry_fact_gate execute(), no host ToolRuntime / no live inventory)');
168
+ }
169
+ await main();
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Flight malformed fixture (#279):
3
+ * 隔离扩展传输车道:注入临时端口桥 + 进程内假扩展客户端(回环直 fetch)。
4
+ * 证明 sessionFlightSearch 的扩展分支对以下 batchSearch 响应都按合同收果:
5
+ * - JSON null body → verdict=error(非合法空,不静默收敛为 miss)
6
+ * - 非数组 flightItineraryList(对象) → verdict=error
7
+ * - 非数组 priceList(item 局部非法) → verdict=error(不暴露畸形行)
8
+ * - 合法空(data.flightItineraryList=[])→ verdict=miss(已识别合法空)
9
+ * - 合法命中 → verdict=hit
10
+ * - 挑战页(title 命 CHALLENGE_RE) → verdict=challenged(红线优先于解析)
11
+ * - 混合有效+畸形行 → verdict=hit(保留有效项)
12
+ * 全程:不开浏览器、不碰登录态、不写共享状态;唯一慢例=扩展会话桥自检(no-spend)。
13
+ */
14
+ import assert from 'node:assert/strict';
15
+ import { mkdtempSync, rmSync } from 'node:fs';
16
+ import { tmpdir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { createSessionBridge, __resetSessionBridgeForTest, __setSessionBridgeForTest, EXTENSION_ORIGIN } from '../capabilities/session/extension-bridge.js';
19
+ import { sessionFlightSearch, __resetRateLimiterForTest } from '../capabilities/session-search.js';
20
+ import { buildEntryUrl } from '../capabilities/session/adapters/ctrip-flight.js';
21
+ let passed = 0;
22
+ async function check(label, fn) {
23
+ await fn();
24
+ passed += 1;
25
+ console.log(` ok - ${label}`);
26
+ }
27
+ async function claimOnce(port, respond) {
28
+ const r = await fetch(`http://127.0.0.1:${port}/jobs`, {
29
+ method: 'POST',
30
+ headers: { 'content-type': 'application/json', origin: EXTENSION_ORIGIN },
31
+ body: JSON.stringify({ extensionVersion: 'flight-malformed-test', capabilities: ['ctrip-flight'] }),
32
+ });
33
+ const data = (await r.json());
34
+ if (data.job && respond) {
35
+ const result = respond(data.job);
36
+ if (result) {
37
+ await fetch(`http://127.0.0.1:${port}/results/${encodeURIComponent(data.job.jobId)}`, {
38
+ method: 'POST',
39
+ headers: { 'content-type': 'application/json', origin: EXTENSION_ORIGIN },
40
+ body: JSON.stringify(result),
41
+ });
42
+ }
43
+ }
44
+ return data;
45
+ }
46
+ async function heartbeat(port) {
47
+ await fetch(`http://127.0.0.1:${port}/health`, { headers: { origin: EXTENSION_ORIGIN } });
48
+ }
49
+ async function main() {
50
+ console.log('§279 flight-malformed fixture(扩展传输车道注入,全离线)');
51
+ // 入口 URL 校验;不查时报 unresolved 错误(这是早于解析的闸)
52
+ const entry = buildEntryUrl('上海', '丽江', '2026-12-01');
53
+ assert.ok(entry.ok && entry.url);
54
+ const url = entry.url;
55
+ // 隔离临时目录(本测试不写真实 profile)
56
+ const tmp = mkdtempSync(join(tmpdir(), 'gotry-flight-malformed-'));
57
+ const bridge = await createSessionBridge({ ports: [0] });
58
+ assert.ok(bridge.ok, `临时桥创建成功: ${bridge.ok ? '' : bridge.summary}`);
59
+ if (!bridge.ok) {
60
+ rmSync(tmp, { recursive: true, force: true });
61
+ process.exit(1);
62
+ }
63
+ const port = bridge.bridge.port;
64
+ __setSessionBridgeForTest(bridge.bridge);
65
+ await heartbeat(port);
66
+ // 单次长跑假扩展客户端:
67
+ // cookie-names → 票据名(用户已登录态)
68
+ // search → 用当前 testCase.body/testCase.title 回包
69
+ // testCase 是可变闭包;每次 expectVerdict 改写它后调用 sessionFlightSearch。
70
+ let testCase = { body: '', title: '' };
71
+ let done = false;
72
+ const claimLoop = (async () => {
73
+ while (!done) {
74
+ const ac = new AbortController();
75
+ const bail = setTimeout(() => ac.abort(), 1_500);
76
+ try {
77
+ await claimOnce(port, (job) => {
78
+ if (job.kind === 'cookie-names')
79
+ return { ok: true, kind: 'cookie-names', names: ['cticket'] };
80
+ if (job.kind === 'search')
81
+ return { ok: true, kind: 'search', body: testCase.body, title: testCase.title };
82
+ return null;
83
+ });
84
+ }
85
+ catch { /* park 中止=常态 */ }
86
+ clearTimeout(bail);
87
+ }
88
+ })();
89
+ try {
90
+ const expectVerdict = async (label, body, title, expected, extra) => {
91
+ __resetRateLimiterForTest();
92
+ testCase = { body, title };
93
+ const r = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 5_000 });
94
+ await check(label, () => {
95
+ assert.equal(r.verdict, expected, `verdict 期望 ${expected},实 ${r.verdict} (${r.error ?? ''})`);
96
+ if (extra)
97
+ extra(r);
98
+ });
99
+ };
100
+ await expectVerdict('JSON null body → error(非合法空,不静默收敛为 miss)', 'null', '机票列表', 'error', (r) => {
101
+ assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
102
+ assert.ok(!r.options, 'error 不带 options');
103
+ assert.ok(r.evidence.includes('[会话:ctrip-flight@error@'), '证据链为错误形');
104
+ });
105
+ await expectVerdict('非数组 flightItineraryList(对象) → error', JSON.stringify({ data: { flightItineraryList: { foo: 1 } } }), '机票列表', 'error', (r) => {
106
+ assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
107
+ });
108
+ await expectVerdict('非数组 flightItineraryList(字符串) → error', JSON.stringify({ data: { flightItineraryList: 'abc' } }), '机票列表', 'error');
109
+ await expectVerdict('非数组 priceList(item 局部非法) → error(不暴露畸形行)', JSON.stringify({ data: { flightItineraryList: [{
110
+ flightSegments: [{ airlineName: '吉祥航空', duration: 200, flightList: [{ flightNo: 'HO1', departureAirportName: '虹桥', arrivalAirportName: '三义', departureDateTime: '2026-12-01 07:35:00', arrivalDateTime: '2026-12-01 10:55:00' }] }],
111
+ priceList: 'not-an-array',
112
+ }] } }), '机票列表', 'error', (r) => {
113
+ assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
114
+ assert.ok(!r.options, 'error 不带 options');
115
+ assert.ok(r.evidence.includes('@error@'), '错误证据锚点存在');
116
+ });
117
+ await expectVerdict('合法空(flightItineraryList=[]) → miss(已识别合法空)', JSON.stringify({ data: { flightItineraryList: [] } }), '机票列表', 'miss', (r) => {
118
+ assert.equal(r.options?.length ?? 0, 0);
119
+ assert.ok(!r.error, '合法空不带 error');
120
+ assert.ok(r.evidence.includes('[会话:ctrip-flight@'), '证据链正常');
121
+ });
122
+ const validHit = JSON.stringify({ data: { flightItineraryList: [{
123
+ flightSegments: [{ airlineName: '吉祥航空', duration: 200, flightList: [{ flightNo: 'HO5577', departureAirportName: '虹桥国际机场', arrivalAirportName: '三义机场', departureDateTime: '2026-12-01 07:35:00', arrivalDateTime: '2026-12-01 10:55:00' }] }],
124
+ priceList: [{ adultPrice: 3240 }, { adultPrice: 2980 }],
125
+ }] } });
126
+ await expectVerdict('合法命中 → hit(flightNo+最低价)', validHit, '机票列表', 'hit', (r) => {
127
+ assert.equal(r.options?.length, 1);
128
+ assert.equal(r.options?.[0]?.flightNo, 'HO5577');
129
+ assert.equal(r.options?.[0]?.price, 2980);
130
+ assert.ok(r.evidence.includes('1 options'), '证据链含选项数');
131
+ });
132
+ await expectVerdict('挑战页(title 命中 验证/滑块) → challenged(红线优先于解析)', 'null', '请完成滑块验证', 'challenged', (r) => {
133
+ assert.ok(r.error?.includes('风控') || r.error?.includes('验证码'));
134
+ });
135
+ await expectVerdict('行全畸形 + 非空数组 → error(不伪装成 miss)', JSON.stringify({ data: { flightItineraryList: [{ flightSegments: [{}] }, 'not-object', null] } }), '机票列表', 'error', (r) => {
136
+ assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
137
+ assert.ok(!r.options, 'error 不带 options');
138
+ assert.ok(r.evidence.includes('@error@'), '错误证据锚点存在');
139
+ });
140
+ await expectVerdict('混合有效+畸形行 → hit(保留有效项,price=0)', JSON.stringify({ data: { flightItineraryList: [
141
+ { flightSegments: [{}], priceList: [{ adultPrice: 'bad' }] },
142
+ { flightSegments: [{ flightList: [{ flightNo: 'HO2', departureDateTime: '2026-12-01 08:00:00' }] }], priceList: [] },
143
+ ] } }), '机票列表', 'hit', (r) => {
144
+ assert.equal(r.options?.length, 1);
145
+ assert.equal(r.options?.[0]?.flightNo, 'HO2');
146
+ assert.equal(r.options?.[0]?.price, 0);
147
+ });
148
+ }
149
+ finally {
150
+ done = true;
151
+ await claimLoop;
152
+ await __resetSessionBridgeForTest();
153
+ __setSessionBridgeForTest(null);
154
+ await bridge.bridge.close();
155
+ rmSync(tmp, { recursive: true, force: true });
156
+ }
157
+ console.log(`\n§279 flight-malformed fixture: ${passed} 段全绿`);
158
+ }
159
+ await main();