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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1788 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +161 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +553 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +676 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/provider-detail-sanitize.js +64 -0
  41. package/dist/capabilities/resilience.js +191 -0
  42. package/dist/capabilities/route-provider-conformance.js +341 -0
  43. package/dist/capabilities/session/action-cache.js +50 -44
  44. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  45. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  46. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  47. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  48. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  49. package/dist/capabilities/session/benchmark.js +189 -0
  50. package/dist/capabilities/session/bridge-ledger.js +224 -0
  51. package/dist/capabilities/session/extension-bridge.js +636 -0
  52. package/dist/capabilities/session/extension-channel.js +109 -0
  53. package/dist/capabilities/session/extension-distribution.js +230 -0
  54. package/dist/capabilities/session/extract.js +31 -21
  55. package/dist/capabilities/session/golden-score.js +114 -0
  56. package/dist/capabilities/session/health-watch.js +215 -0
  57. package/dist/capabilities/session/read-guard.js +75 -24
  58. package/dist/capabilities/session/static-flight-golden.js +150 -0
  59. package/dist/capabilities/session/transport.js +97 -83
  60. package/dist/capabilities/session/wizard.js +117 -0
  61. package/dist/capabilities/session-consent.js +178 -0
  62. package/dist/capabilities/session-login.js +170 -0
  63. package/dist/capabilities/session-search.js +635 -44
  64. package/dist/capabilities/spawn-bounded.js +203 -0
  65. package/dist/capabilities/sponsor-plugin.js +341 -0
  66. package/dist/capabilities/visa-policy.js +140 -0
  67. package/dist/capabilities/weather.js +243 -138
  68. package/dist/data/session-golden-20.json +25 -0
  69. package/dist/data/sf-golden-manifest.json +103 -0
  70. package/dist/data/sf-static-routes.json +91 -0
  71. package/dist/scripts/action-cache-tests.js +24 -59
  72. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  73. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  74. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  75. package/dist/scripts/agent-reach-error-tests.js +354 -0
  76. package/dist/scripts/agent-reach-tests.js +26 -18
  77. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  78. package/dist/scripts/anything-path-measure-tests.js +133 -0
  79. package/dist/scripts/anything-path-measure.js +150 -0
  80. package/dist/scripts/anything-tests.js +250 -60
  81. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  82. package/dist/scripts/artifacts-capability-tests.js +727 -0
  83. package/dist/scripts/async-collect.js +50 -11
  84. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  85. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  86. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  87. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  88. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  89. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  90. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  91. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  92. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  93. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  94. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  95. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
  96. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  97. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  98. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  99. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  100. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  101. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  102. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  103. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  104. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  105. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  106. package/dist/scripts/booking-executor-tests.js +80 -0
  107. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  108. package/dist/scripts/booking-saga-tests.js +125 -0
  109. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  110. package/dist/scripts/bootstrap-tests.js +1549 -0
  111. package/dist/scripts/bridge-events-tests.js +239 -0
  112. package/dist/scripts/bridge-ledger-tests.js +428 -0
  113. package/dist/scripts/build-changelog.js +238 -0
  114. package/dist/scripts/build-metrics-report.js +331 -0
  115. package/dist/scripts/changelog-tests.js +118 -0
  116. package/dist/scripts/channel-probe-tests.js +103 -0
  117. package/dist/scripts/channel-probe.js +169 -0
  118. package/dist/scripts/channel-registry-tests.js +208 -0
  119. package/dist/scripts/city-scenario-tier-tests.js +234 -0
  120. package/dist/scripts/companion-tests.js +25 -70
  121. package/dist/scripts/dida-cookie-probe.js +87 -0
  122. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  123. package/dist/scripts/dida-sw-probe.js +114 -0
  124. package/dist/scripts/diff-test.js +17 -12
  125. package/dist/scripts/doctor-tests.js +600 -0
  126. package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
  127. package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
  128. package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
  129. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  130. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  131. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  132. package/dist/scripts/dsh-llm-tests.js +437 -0
  133. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  134. package/dist/scripts/dsh-runtime-closure.js +162 -0
  135. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  136. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  137. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  138. package/dist/scripts/effect-tests.js +539 -0
  139. package/dist/scripts/engine-run.js +1 -3
  140. package/dist/scripts/engine-tests.js +16 -10
  141. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  142. package/dist/scripts/evaluation-contract-tests.js +268 -0
  143. package/dist/scripts/extension-distribution-cli.js +39 -0
  144. package/dist/scripts/extension-distribution-tests.js +443 -0
  145. package/dist/scripts/extension-tests.js +988 -0
  146. package/dist/scripts/external-event-tests.js +350 -0
  147. package/dist/scripts/fact-gate-tests.js +869 -0
  148. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  149. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  150. package/dist/scripts/flight-malformed-tests.js +159 -0
  151. package/dist/scripts/flyai-setup-tests.js +228 -0
  152. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  153. package/dist/scripts/flyai-tests.js +692 -0
  154. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  155. package/dist/scripts/fx-contract-tests.js +201 -0
  156. package/dist/scripts/g5-guard-tests.js +150 -0
  157. package/dist/scripts/g5-guard.js +193 -0
  158. package/dist/scripts/gen-lunar-anchors.js +106 -0
  159. package/dist/scripts/geo-atlas-tests.js +283 -0
  160. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  161. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  162. package/dist/scripts/gotry-try-demo.js +49 -0
  163. package/dist/scripts/ground-transfer-tests.js +956 -0
  164. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  165. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  166. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  167. package/dist/scripts/hbcli-tests.js +121 -69
  168. package/dist/scripts/health-watch-cli.js +73 -0
  169. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  170. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  171. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  172. package/dist/scripts/i18n-tests.js +41 -0
  173. package/dist/scripts/incident-tests.js +281 -87
  174. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  175. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  176. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  177. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  178. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  179. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  180. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  181. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  182. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  183. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  184. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  185. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  186. package/dist/scripts/itinerary-deck-tests.js +435 -0
  187. package/dist/scripts/itinerary-html-tests.js +426 -0
  188. package/dist/scripts/journey-tests.js +22 -27
  189. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  190. package/dist/scripts/kernel-manifest-gate.js +126 -0
  191. package/dist/scripts/kernel-manifest-tests.js +212 -0
  192. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  193. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  194. package/dist/scripts/kernel-manifest.js +369 -0
  195. package/dist/scripts/lavish-local-tests.js +1226 -0
  196. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  197. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  198. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  199. package/dist/scripts/ledger-tests.js +382 -246
  200. package/dist/scripts/ledger-workflow-crash.js +10 -20
  201. package/dist/scripts/m3-cohort-tests.js +714 -0
  202. package/dist/scripts/m3-cohort.js +310 -0
  203. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  204. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  205. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  206. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  207. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  208. package/dist/scripts/memory-capture-tests.js +242 -61
  209. package/dist/scripts/memory-decay-tests.js +18 -52
  210. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  211. package/dist/scripts/memory-lifecycle.js +196 -0
  212. package/dist/scripts/memory-metrics.js +24 -12
  213. package/dist/scripts/memory-value-report.js +631 -0
  214. package/dist/scripts/memory-value-tests.js +328 -0
  215. package/dist/scripts/metrics-report-tests.js +151 -0
  216. package/dist/scripts/model-override-e2e.js +137 -0
  217. package/dist/scripts/nightly-evidence-tests.js +203 -0
  218. package/dist/scripts/nightly-evidence.js +303 -0
  219. package/dist/scripts/nudge-digest.js +39 -28
  220. package/dist/scripts/observability-tests.js +381 -0
  221. package/dist/scripts/onboarding-tests.js +161 -0
  222. package/dist/scripts/opensky-check.js +18 -13
  223. package/dist/scripts/opensky-tests.js +28 -28
  224. package/dist/scripts/outcome-projection-tests.js +427 -0
  225. package/dist/scripts/persona-sim-tests.js +603 -0
  226. package/dist/scripts/persona-sim.js +1081 -0
  227. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  228. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  229. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  230. package/dist/scripts/price-drift-tests.js +340 -0
  231. package/dist/scripts/price-drift-watch.js +447 -0
  232. package/dist/scripts/probe-poi-tests.js +53 -45
  233. package/dist/scripts/product-metrics.js +483 -0
  234. package/dist/scripts/publish-preverify.js +110 -0
  235. package/dist/scripts/realtime-pricing-tests.js +76 -0
  236. package/dist/scripts/recall-tests.js +513 -0
  237. package/dist/scripts/replay-async.js +22 -18
  238. package/dist/scripts/replay-real.js +18 -18
  239. package/dist/scripts/replay.js +54 -82
  240. package/dist/scripts/route-provider-conformance-tests.js +391 -0
  241. package/dist/scripts/session-attach-diagnose.js +20 -20
  242. package/dist/scripts/session-attach-poc.js +34 -37
  243. package/dist/scripts/session-benchmark.js +180 -0
  244. package/dist/scripts/session-dida-live-e2e.js +199 -0
  245. package/dist/scripts/session-extract-tests.js +14 -35
  246. package/dist/scripts/session-link-tests.js +478 -0
  247. package/dist/scripts/session-login.js +27 -46
  248. package/dist/scripts/session-tests.js +796 -96
  249. package/dist/scripts/session-zone-crash.js +49 -0
  250. package/dist/scripts/session-zone-ledger-tests.js +432 -0
  251. package/dist/scripts/session-zone-observation-tests.js +492 -0
  252. package/dist/scripts/session-zone-observe.js +135 -0
  253. package/dist/scripts/session-zone-wiring-tests.js +566 -0
  254. package/dist/scripts/session-zones-tests.js +374 -0
  255. package/dist/scripts/sf-live-benchmark.js +367 -0
  256. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  257. package/dist/scripts/sf-live-cli-tests.js +92 -0
  258. package/dist/scripts/sf-live-options.js +46 -0
  259. package/dist/scripts/sf-manifest-tests.js +30 -0
  260. package/dist/scripts/sf-manifest.js +33 -0
  261. package/dist/scripts/sf-soft-score-tests.js +67 -0
  262. package/dist/scripts/sf-summary-tests.js +324 -0
  263. package/dist/scripts/sf-summary.js +525 -0
  264. package/dist/scripts/share-tests.js +432 -0
  265. package/dist/scripts/skeleton-check.js +16 -14
  266. package/dist/scripts/skeleton-integration-test.js +28 -11
  267. package/dist/scripts/skills-contract-tests.js +51 -46
  268. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  269. package/dist/scripts/smoke.js +690 -246
  270. package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
  271. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  272. package/dist/scripts/state-cli-tests.js +226 -101
  273. package/dist/scripts/state-cli.js +565 -198
  274. package/dist/scripts/state-repair-cli.js +66 -0
  275. package/dist/scripts/state-repair-tests.js +140 -0
  276. package/dist/scripts/static-golden-tests.js +165 -0
  277. package/dist/scripts/time-eval-tests.js +212 -192
  278. package/dist/scripts/travel-timeline-tests.js +29 -93
  279. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  280. package/dist/scripts/turn-handoff-collect.js +161 -0
  281. package/dist/scripts/turn-policy-tests.js +55 -0
  282. package/dist/scripts/typed-contract-canary.js +176 -0
  283. package/dist/scripts/tz-resolver-sanity.js +50 -0
  284. package/dist/scripts/tz-resolver-tests.js +584 -0
  285. package/dist/scripts/unified-tests.js +60 -20
  286. package/dist/scripts/visa-policy-tests.js +62 -0
  287. package/dist/scripts/weather-tests.js +276 -41
  288. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  289. package/dist/scripts/write-gate-tests.js +522 -0
  290. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  291. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  292. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  293. package/dist/scripts/z3-race-tests.js +46 -0
  294. package/dist/src/artifact-gate.js +829 -0
  295. package/dist/src/backend/kernel.js +91 -0
  296. package/dist/src/backend/modules/booking-copilot.js +92 -0
  297. package/dist/src/backend/modules/booking-executor.js +124 -0
  298. package/dist/src/backend/modules/session-search.js +551 -0
  299. package/dist/src/benchmark-agent-conformance.js +578 -0
  300. package/dist/src/benchmark-environment-bridge.js +515 -0
  301. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  302. package/dist/src/benchmark-tool-isolation.js +151 -0
  303. package/dist/src/bookable-facts.js +533 -0
  304. package/dist/src/booking-saga.js +97 -0
  305. package/dist/src/booking-surface/availability-policy.js +697 -0
  306. package/dist/src/booking-surface/booking-intent.js +43 -0
  307. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  308. package/dist/src/booking-surface/canonical-schema.js +116 -0
  309. package/dist/src/booking-surface/contracts.js +75 -0
  310. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  311. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  312. package/dist/src/booking-surface/dsh-worker.js +98 -0
  313. package/dist/src/booking-surface/error-codes.js +231 -0
  314. package/dist/src/booking-surface/index.js +9 -0
  315. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  316. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  317. package/dist/src/booking-surface/profile.js +24 -0
  318. package/dist/src/booking-surface/recovery-chain.js +367 -0
  319. package/dist/src/booking-surface/runtime.js +1968 -0
  320. package/dist/src/booking-surface/server.js +674 -0
  321. package/dist/src/booking-surface/startup.js +168 -0
  322. package/dist/src/booking-surface/validation.js +261 -0
  323. package/dist/src/bridge.js +20 -13
  324. package/dist/src/city-scenario-tier.js +378 -0
  325. package/dist/src/companions.js +48 -74
  326. package/dist/src/contracts.js +21 -43
  327. package/dist/src/dsh-llm.js +171 -115
  328. package/dist/src/engine.js +276 -193
  329. package/dist/src/evaluation-cadence.js +97 -0
  330. package/dist/src/evaluation-contracts.js +283 -0
  331. package/dist/src/flight-pack-adapter.js +54 -0
  332. package/dist/src/flight-pack-contract.js +2 -0
  333. package/dist/src/flyai-setup-tool.js +70 -0
  334. package/dist/src/gotry-backend.js +125 -0
  335. package/dist/src/hotel-date-gate.js +118 -0
  336. package/dist/src/i18n.js +88 -0
  337. package/dist/src/index.js +2385 -881
  338. package/dist/src/itinerary-deck.js +139 -0
  339. package/dist/src/itinerary-doc-shared.js +661 -0
  340. package/dist/src/itinerary-html.js +131 -0
  341. package/dist/src/journey.js +155 -108
  342. package/dist/src/lavish-tools.js +400 -0
  343. package/dist/src/ledger-repair-apply.js +538 -0
  344. package/dist/src/ledger-repair-plan.js +463 -0
  345. package/dist/src/loop.js +331 -161
  346. package/dist/src/m3-cohort.js +1577 -0
  347. package/dist/src/memory-capture.js +180 -32
  348. package/dist/src/memory-decay.js +30 -14
  349. package/dist/src/memory-lifecycle.js +1310 -0
  350. package/dist/src/memory-utility.js +27 -40
  351. package/dist/src/mock-llm.js +36 -61
  352. package/dist/src/model.js +81 -36
  353. package/dist/src/outcome-projection.js +746 -0
  354. package/dist/src/realtime-pricing.js +103 -0
  355. package/dist/src/recall/card.js +74 -0
  356. package/dist/src/recall/evaluator.js +140 -0
  357. package/dist/src/recall/integration.js +17 -0
  358. package/dist/src/recall/tick.js +166 -0
  359. package/dist/src/session-link/plan-it-action.js +48 -0
  360. package/dist/src/session-link/session-link.js +248 -0
  361. package/dist/src/session-zone-ledger.js +312 -0
  362. package/dist/src/session-zone-observation.js +354 -0
  363. package/dist/src/session-zone-wiring.js +658 -0
  364. package/dist/src/session-zones.js +510 -0
  365. package/dist/src/share/adapters.js +51 -0
  366. package/dist/src/share/share-consent.js +76 -0
  367. package/dist/src/share/share-deck.js +58 -0
  368. package/dist/src/share/share-token.js +146 -0
  369. package/dist/src/slot-spec.js +84 -106
  370. package/dist/src/state-ledger.js +696 -524
  371. package/dist/src/state-repair.js +299 -0
  372. package/dist/src/subagent-job-id-guard.js +58 -0
  373. package/dist/src/time-anchor.js +231 -33
  374. package/dist/src/tool-packet.js +20 -8
  375. package/dist/src/travel-slots.js +58 -35
  376. package/dist/src/travel-timeline.js +44 -63
  377. package/dist/src/turn-deadline.js +287 -0
  378. package/dist/src/turn-policy.js +118 -0
  379. package/dist/src/tz-resolver.js +143 -0
  380. package/dist/src/unified.js +600 -360
  381. package/dist/src/wish-pool.js +42 -12
  382. package/dist/src/write-gate.js +495 -0
  383. package/dist/src/z3-shared.js +284 -0
  384. package/extension/README.md +21 -0
  385. package/extension/README.zh-CN.md +21 -0
  386. package/extension/background.js +463 -0
  387. package/extension/content-bridge.js +270 -0
  388. package/extension/content-main.js +111 -0
  389. package/extension/manifest.json +60 -0
  390. package/package.json +407 -14
  391. package/schemas/booking.intent.schema.json +193 -0
  392. package/schemas/booking.surface.schema.json +2622 -0
  393. package/ts/capabilities/agent-reach-deep.ts +114 -0
  394. package/ts/capabilities/agent-reach.ts +198 -0
  395. package/ts/capabilities/anything.ts +282 -0
  396. package/ts/capabilities/artifacts.ts +428 -0
  397. package/ts/capabilities/fact-log.ts +54 -0
  398. package/ts/capabilities/flyai-config.ts +388 -0
  399. package/ts/capabilities/flyai.ts +1274 -0
  400. package/ts/capabilities/hbcli.ts +253 -71
  401. package/ts/capabilities/incident-log.ts +88 -59
  402. package/ts/capabilities/itinerary-artifact.ts +214 -0
  403. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  404. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  405. package/ts/capabilities/opensky.ts +121 -0
  406. package/ts/capabilities/session/action-cache.ts +148 -0
  407. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  408. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  409. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  410. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  411. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  412. package/ts/capabilities/session/benchmark.ts +276 -0
  413. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  414. package/ts/capabilities/session/extension-bridge.ts +794 -0
  415. package/ts/capabilities/session/extension-channel.ts +144 -0
  416. package/ts/capabilities/session/extension-distribution.ts +266 -0
  417. package/ts/capabilities/session/extract.ts +54 -0
  418. package/ts/capabilities/session/golden-score.ts +139 -0
  419. package/ts/capabilities/session/health-watch.ts +264 -0
  420. package/ts/capabilities/session/read-guard.ts +141 -0
  421. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  422. package/ts/capabilities/session/transport.ts +146 -0
  423. package/ts/capabilities/session/wizard.ts +140 -0
  424. package/ts/capabilities/session-consent.ts +211 -0
  425. package/ts/capabilities/session-login.ts +208 -0
  426. package/ts/capabilities/session-search.ts +893 -0
  427. package/ts/capabilities/weather.ts +330 -0
  428. package/ts/dsh-runtime/vendor/README.md +68 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  440. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  441. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  442. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  443. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  444. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  445. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  446. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  447. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  448. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  449. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  450. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  451. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  452. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  453. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  454. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  455. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  456. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  457. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  458. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  459. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  460. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  461. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  462. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  463. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  464. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  465. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  466. package/ts/package.json +293 -3
  467. package/ts/scripts/async-collect.ts +91 -0
  468. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  469. package/ts/scripts/skeleton-check.ts +1 -1
  470. package/ts/scripts/skeleton-integration-test.ts +23 -1
  471. package/ts/scripts/state-cli.ts +586 -0
  472. package/ts/scripts/turn-handoff-collect.ts +178 -0
  473. package/ts/src/artifact-gate.ts +991 -0
  474. package/ts/src/benchmark-agent-conformance.ts +619 -0
  475. package/ts/src/benchmark-environment-bridge.ts +538 -0
  476. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  477. package/ts/src/benchmark-tool-isolation.ts +166 -0
  478. package/ts/src/bookable-facts.ts +837 -0
  479. package/ts/src/booking-saga.ts +133 -0
  480. package/ts/src/booking-surface/availability-policy.ts +522 -0
  481. package/ts/src/booking-surface/booking-intent.ts +86 -0
  482. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  483. package/ts/src/booking-surface/canonical-schema.js +116 -0
  484. package/ts/src/booking-surface/contracts.ts +475 -0
  485. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  486. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  487. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  488. package/ts/src/booking-surface/error-codes.ts +241 -0
  489. package/ts/src/booking-surface/index.ts +9 -0
  490. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  491. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  492. package/ts/src/booking-surface/profile.ts +42 -0
  493. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  494. package/ts/src/booking-surface/runtime.ts +1793 -0
  495. package/ts/src/booking-surface/server.ts +733 -0
  496. package/ts/src/booking-surface/startup.ts +201 -0
  497. package/ts/src/booking-surface/validation.ts +217 -0
  498. package/ts/src/companions.ts +113 -0
  499. package/ts/src/contracts.ts +17 -3
  500. package/ts/src/dsh-llm.ts +152 -17
  501. package/ts/src/flyai-setup-tool.ts +77 -0
  502. package/ts/src/index.ts +2123 -200
  503. package/ts/src/itinerary-deck.ts +189 -0
  504. package/ts/src/itinerary-doc-shared.ts +741 -0
  505. package/ts/src/itinerary-html.ts +171 -0
  506. package/ts/src/loop.ts +234 -26
  507. package/ts/src/memory-capture.ts +283 -0
  508. package/ts/src/memory-decay.ts +55 -0
  509. package/ts/src/memory-utility.ts +70 -0
  510. package/ts/src/mock-llm.ts +4 -8
  511. package/ts/src/model.ts +88 -2
  512. package/ts/src/recall/card.ts +106 -0
  513. package/ts/src/recall/evaluator.ts +170 -0
  514. package/ts/src/recall/integration.ts +35 -0
  515. package/ts/src/recall/tick.ts +213 -0
  516. package/ts/src/session-link/plan-it-action.ts +78 -0
  517. package/ts/src/session-link/session-link.ts +275 -0
  518. package/ts/src/share/adapters.ts +93 -0
  519. package/ts/src/share/share-consent.ts +105 -0
  520. package/ts/src/share/share-deck.ts +85 -0
  521. package/ts/src/share/share-token.ts +166 -0
  522. package/ts/src/slot-spec.ts +172 -0
  523. package/ts/src/state-ledger.ts +1166 -0
  524. package/ts/src/subagent-job-id-guard.ts +72 -0
  525. package/ts/src/time-anchor.ts +338 -0
  526. package/ts/src/tool-packet.ts +32 -0
  527. package/ts/src/travel-slots.ts +219 -0
  528. package/ts/src/travel-timeline.ts +83 -0
  529. package/ts/src/turn-deadline.ts +366 -0
  530. package/ts/src/turn-policy.ts +154 -0
  531. package/ts/src/unified.ts +444 -105
  532. package/ts/src/wish-pool.ts +79 -0
  533. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,49 @@
1
+ /**
2
+ * persona 表层护栏测试(issue #192/#2/#194 回归锚;2026-09-11 契约瘦身改锚,
3
+ * 全离线,只读仓根 patch):
4
+ * 1. (10) 域边界含表层规则句——gotry 能力一律是工具调用,绝不把 gotry_* 传给
5
+ * skill 加载器(dsh skill 面名合法域 ^[a-z0-9]+(-[a-z0-9]+)*$,下划线必抛
6
+ * invalid skill name,rc18 真实用户踩坑);
7
+ * 2. 行为契约 13 条编号完整(2026-09-11 瘦身 23→13,防后续编辑吞条目;窄域
8
+ * 细则归位工具描述/路由卡,persona 只留跨场景行为契约);
9
+ * 3. skill 失败后的行为指引存在(改回 tool call,不换名重试 skill);
10
+ * 4. (4) 候选时段示例枚举绑定锚点卡——澄清卡/访谈/选项不列已过节日
11
+ * (#2 rc18 真实复发:卡把已过的春节/清明/五一当「想用的时候」示例);
12
+ * 5. (13) 后台协作——子任务等待纪律(#194 rc19 实踩:子代理回执 id 不是
13
+ * job id,job_output 轮询子代理 = unknown job 硬错误)+ 永不宣称「工具
14
+ * 预算已耗尽」(ADR-24 v2:唯一边界是运行时时间闸);
15
+ * 6. 动态变量注入面完整——current_date/time_anchor_card/motivation_brief/
16
+ * channel_routing_card 四个引用缺一不可(瘦身不得吞注入面)。
17
+ *
18
+ * 运行: cd ts && npx tsx scripts/persona-surface-guard-tests.ts
19
+ */
20
+ import assert from 'node:assert/strict';
21
+ import { readFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ const patch = readFileSync(join(process.cwd(), '..', 'cordis.gotry-patch.yml'), 'utf-8');
24
+ // 1. 表层规则句存在且仅一次
25
+ const guard = '表层规则:gotry 的全部能力一律是【工具调用】';
26
+ assert.equal((patch.match(new RegExp(guard, 'g')) ?? []).length, 1, '表层规则句应恰好出现一次');
27
+ assert.ok(patch.includes('绝不把 gotry_* 名字传给 skill 加载器'), '应显式禁止 gotry_* 进 skill 加载器');
28
+ // 2. 行为契约 13 条编号完整(2026-09-11 瘦身 23→13)
29
+ const markers = new Set([...patch.matchAll(/\((\d{1,2})\)/g)].map((m) => Number(m[1])));
30
+ for (let i = 1; i <= 13; i += 1) {
31
+ assert.ok(markers.has(i), `行为契约 (${i}) 应存在`);
32
+ }
33
+ assert.equal(markers.size, 13, `契约条目应恰为 13 条,实际 ${markers.size}`);
34
+ // 3. skill 用错面后的行为指引
35
+ assert.ok(patch.includes('改回 tool call'), '应指引失败后改回 tool call');
36
+ // 4. #2 复发回归锚:澄清卡示例枚举同样过锚点卡(契约 (4) 内部澄清)
37
+ assert.ok(patch.includes('候选时段示例'), '#2 回归:候选时段示例条款应存在');
38
+ assert.ok(patch.includes('已过的节日不进示例枚举'), '应禁止已过节日进澄清卡示例枚举');
39
+ // 5. #194 回归锚:子任务等待纪律((13) 后台协作)
40
+ assert.ok(patch.includes('(13)后台协作'), '#194 回归:后台协作条款应存在');
41
+ assert.ok(patch.includes('对子代理绝不调用 job_output/job_kill'), '应禁止对子代理调用 job_output/job_kill');
42
+ assert.ok(patch.includes('job_output/job_kill 只用于'), '应限定 job_output/job_kill 的适用面');
43
+ // ADR-24 v2:工具循环唯一边界是运行时时间闸
44
+ assert.ok(patch.includes('工具预算已耗尽'), 'ADR-24 v2:永不宣称工具预算耗尽的指引应存在');
45
+ // 6. 动态变量注入面完整(瘦身不得吞注入面)
46
+ for (const v of ['{{current_date}}', '{{time_anchor_card}}', '{{motivation_brief}}', '{{channel_routing_card}}']) {
47
+ assert.ok(patch.includes(v), `persona 应保留动态变量引用 ${v}`);
48
+ }
49
+ console.log('PERSONA SURFACE GUARD TESTS: 6/6 OK(表层规则句 / 13 条契约完整 / skill 失败行为指引 / 澄清卡示例过锚点卡 / 子任务等待纪律+预算话术 / 动态变量注入面)');
@@ -0,0 +1,17 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { REQUIRED_BENCHMARK_DSH_VERSION } from '../../bin/gotry-runtime-resolution.js';
5
+ import { parsePnpmDshLock, REQUIRED_DSH_RUNTIME_PACKAGE_COUNT, validateDshRuntimeClosure, } from './dsh-runtime-closure.js';
6
+ const consumerRoot = process.argv[2];
7
+ assert.ok(consumerRoot, 'usage: pnpm-dsh-closure-proof.ts <clean-consumer-root>');
8
+ const installedPackage = JSON.parse(readFileSync(resolve(consumerRoot, 'node_modules/@danceiny/gotry/package.json'), 'utf8'));
9
+ const lockPackages = parsePnpmDshLock(readFileSync(resolve(consumerRoot, 'pnpm-lock.yaml'), 'utf8'));
10
+ const closure = validateDshRuntimeClosure({
11
+ dependencies: installedPackage.dependencies ?? {},
12
+ lockPackages,
13
+ runtimeVersion: REQUIRED_BENCHMARK_DSH_VERSION,
14
+ expectedPackageCount: REQUIRED_DSH_RUNTIME_PACKAGE_COUNT,
15
+ });
16
+ assert.equal(closure.names.length, REQUIRED_DSH_RUNTIME_PACKAGE_COUNT, 'clean pnpm consumer must resolve the complete Round 5 DSH closure');
17
+ console.log(`PNPM DSH CLOSURE PROOF: ${closure.names.length} packages at ${closure.version}`);
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Issue #359 — 政策锚点全字段内容指纹(注册 `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
+ * → `itinerary.trip_start` 桥接(issue #359 finding 2),完整覆盖公开闸侧路径。
8
+ * 不启动 dsh 宿主、不调用真实 ToolRuntime、不触碰供应商 / browser / policy
9
+ * service / 共享状态。
10
+ *
11
+ * 运行(在 ts/ 下):
12
+ * PATH=/opt/homebrew/opt/node@24/bin:$PATH GOTRY_SESSION_LIVE=0 \
13
+ * npx tsx scripts/policy-anchor-359-registered-e2e.ts
14
+ */
15
+ import assert from 'node:assert/strict';
16
+ import { mkdtempSync, rmSync, mkdirSync } from 'node:fs';
17
+ import { tmpdir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { apply } from '../src/index.js';
20
+ import { BOOKABLE_FACT_SCHEMA, makeFactId, renderPolicyFact, } from '../src/bookable-facts.js';
21
+ import { appendFacts } from '../capabilities/fact-log.js';
22
+ const FETCHED = '2026-09-10T00:00:00.000Z';
23
+ async function main() {
24
+ const home = mkdtempSync(join(tmpdir(), 'gotry-policy-359-registered-'));
25
+ const stateRoot = join(home, 'state');
26
+ const cwd = join(home, 'workspace');
27
+ mkdirSync(stateRoot, { recursive: true });
28
+ mkdirSync(cwd, { recursive: true });
29
+ const policy = {
30
+ schema: BOOKABLE_FACT_SCHEMA,
31
+ fact_id: makeFactId(['policy-359', 'reg', '泰国免签']),
32
+ kind: 'policy',
33
+ subject: '泰国入境(中国护照)',
34
+ statement: '免签停留(口径以泰方公告为准);UAE 居民返程应优先校验 residence visa / Emirates ID 而非游客免签口径',
35
+ source: 'web:official',
36
+ query_id: 'web:policy:泰国免签',
37
+ fetched_at: FETCHED,
38
+ as_of: '2026-08-29',
39
+ };
40
+ // 走真实事实日志路径(append-only JSONL),与产品 gotry_flyai_search /
41
+ // gotry_session_search 同款事实落账接口;闸侧 loadFactRegistry 直接读这里。
42
+ await appendFacts(stateRoot, [policy]);
43
+ // 装载 gotry-tools 插件 → 捕获 gotry_fact_gate 的真实 execute。
44
+ const registered = [];
45
+ const ctx = {
46
+ tools: { register: (t) => registered.push(t) },
47
+ systemPrompt: { section: () => undefined, variable: () => undefined },
48
+ on: () => () => undefined,
49
+ };
50
+ apply(ctx, {
51
+ stateRoot,
52
+ timeoutMs: 30_000,
53
+ hbcliBin: 'hbcli-not-on-path',
54
+ sessionAccess: 'off',
55
+ });
56
+ const factGate = registered.find(t => t.name === 'gotry_fact_gate');
57
+ if (!factGate)
58
+ throw new Error(`FAIL: 注册定义列表缺少 gotry_fact_gate,实际注册: ${registered.map(t => t.name).join(',')}`);
59
+ const exec = { agent: { session: { header: { cwd } } } };
60
+ // Case 1:canonical no-reminder 行经真实 gotry_fact_gate → pass。
61
+ const canonicalLine = renderPolicyFact(policy);
62
+ const c1Artifact = ['## 政策', canonicalLine].join('\n');
63
+ const r1 = await factGate.execute({ markdown: c1Artifact }, exec);
64
+ assert.equal(r1.verdict, 'pass', `canonical no-reminder 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r1.verdict, summary: r1.summary })}`);
65
+ assert.equal(r1.traceable, 1);
66
+ console.log(` ok - 注册 gotry_fact_gate canonical no-reminder → pass`);
67
+ // Case 2:legitimate review_by(事实自带)— 无 trip_start,canonical body 已含复核提醒。
68
+ const reviewByPolicy = { ...policy, fact_id: makeFactId(['policy-359', 'reg', 'review-by']), review_by: '2027-06-16' };
69
+ await appendFacts(stateRoot, [reviewByPolicy]);
70
+ const reviewByLine = renderPolicyFact(reviewByPolicy);
71
+ const r2 = await factGate.execute({ markdown: ['## 政策', reviewByLine].join('\n') }, exec);
72
+ assert.equal(r2.verdict, 'pass', `legitimate review_by 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r2.verdict, summary: r2.summary })}`);
73
+ console.log(` ok - 注册 gotry_fact_gate legitimate review_by → pass`);
74
+ // Case 3:legitimate tripStart-derived reminder — 通过已加载 itinerary.trip_start 桥接
75
+ // (不要求调用方额外传 opts.tripStart;闸侧自动从 itinerary 派)。
76
+ // itinerary 必须保持自身不变量合法(nights/legs),否则闸会被 itineraryInvariants
77
+ // 单独阻断,影响 policy 收口的可读性。这里给一个合法的同日往返零夜行程。
78
+ const tripStart = '2027-07-16';
79
+ const tripStartLine = renderPolicyFact(policy, tripStart);
80
+ const r3 = await factGate.execute({
81
+ markdown: ['## 政策', tripStartLine].join('\n'),
82
+ itinerary: {
83
+ trip_start: tripStart,
84
+ trip_end: tripStart,
85
+ stays: [],
86
+ onboard_nights: 0,
87
+ od_segments: [],
88
+ budget_items: [],
89
+ },
90
+ }, exec);
91
+ assert.equal(r3.verdict, 'pass', `legitimate tripStart-derived reminder 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r3.verdict, summary: r3.summary })}`);
92
+ console.log(` ok - 注册 gotry_fact_gate legitimate tripStart reminder(经 itinerary.trip_start 桥接)→ pass`);
93
+ // Case 4:trailing-after-anchor 攻击:合法 fact_id + 锚点后追加相反政策正文 → blocked。
94
+ const trailingLine = `${canonicalLine} 反而是落地签,需提前办签证`;
95
+ const r4 = await factGate.execute({ markdown: ['## 政策', trailingLine].join('\n') }, exec);
96
+ assert.equal(r4.verdict, 'blocked', `trailing-after-anchor 经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r4.verdict, summary: r4.summary })}`);
97
+ assert.ok(r4.violations?.some(v => v.kind === 'fact_anchor_unknown'), `trailing-after-anchor 应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r4.violations?.map(v => v.kind))}`);
98
+ console.log(` ok - 注册 gotry_fact_gate trailing-after-anchor 攻击 → blocked/fact_anchor_unknown`);
99
+ // Case 5:subject 篡改(原 main 静默 pass 的反例)。
100
+ const tamperedSubject = canonicalLine.replace('泰国入境(中国护照)', '另一对象的政策');
101
+ const r5 = await factGate.execute({ markdown: ['## 政策', tamperedSubject].join('\n') }, exec);
102
+ assert.equal(r5.verdict, 'blocked', `subject 篡改经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r5.verdict, summary: r5.summary })}`);
103
+ assert.ok(r5.violations?.some(v => v.kind === 'fact_anchor_unknown'), `subject 篡改应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r5.violations?.map(v => v.kind))}`);
104
+ console.log(` ok - 注册 gotry_fact_gate subject 篡改 → blocked/fact_anchor_unknown`);
105
+ // Case 6:opposite statement 篡改 — 现实威胁:沿用合法 fact_id + 锚点改写 statement 方向。
106
+ const oppositeStatement = canonicalLine.replace('免签停留', '不免签,需提前办签证');
107
+ const r6 = await factGate.execute({ markdown: ['## 政策', oppositeStatement].join('\n') }, exec);
108
+ assert.equal(r6.verdict, 'blocked', `opposite statement 篡改经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r6.verdict, summary: r6.summary })}`);
109
+ assert.ok(r6.violations?.some(v => v.kind === 'fact_anchor_unknown'), `opposite statement 应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r6.violations?.map(v => v.kind))}`);
110
+ console.log(` ok - 注册 gotry_fact_gate opposite statement 篡改 → blocked/fact_anchor_unknown`);
111
+ // Case 7:provenance 篡改(source / fetched_at / query_id 任一)→ blocked。
112
+ const tamperedProv = canonicalLine
113
+ .replace('[web:official@', '[synthetic:official@')
114
+ .replace(FETCHED, '2026-09-01T00:00:00.000Z')
115
+ .replace('#web:policy:泰国免签', '#synthetic:policy:泰国免签');
116
+ const r7 = await factGate.execute({ markdown: ['## 政策', tamperedProv].join('\n') }, exec);
117
+ assert.equal(r7.verdict, 'blocked', `provenance 篡改经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r7.verdict, summary: r7.summary })}`);
118
+ assert.ok(r7.violations?.some(v => v.kind === 'fact_anchor_unknown'), `provenance 篡改应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r7.violations?.map(v => v.kind))}`);
119
+ console.log(` ok - 注册 gotry_fact_gate provenance(source/fetched_at/query_id)篡改 → blocked/fact_anchor_unknown`);
120
+ // Compatibility(issue #359 final repair):注册闸侧既不传 itinerary、调用方也不显式
121
+ // opts.tripStart 时,renderer 给的两类合法 canonical form 必须经真实 gotry_fact_gate
122
+ // pass;否则(伪 reminder 日期、body 篡改、review_by 改写、trailing-after-anchor)
123
+ // 必须仍走 fact_anchor_unknown。证明兼容性回退收敛于 renderer 自身的输出空间,
124
+ // 不引入新公共 API,也不放行任意政策文本。
125
+ const compatExec = exec;
126
+ // Case 8:context-free canonical no-reminder 经注册闸 → pass
127
+ // (renderer 不接 tripStart,policy 也无 review_by → 不应出现 reminder 段)
128
+ const r8 = await factGate.execute({ markdown: ['## 政策', canonicalLine].join('\n') }, compatExec);
129
+ assert.equal(r8.verdict, 'pass', `context-free no-reminder 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r8.verdict, summary: r8.summary })}`);
130
+ assert.equal(r8.traceable, 1);
131
+ console.log(` ok - 注册 gotry_fact_gate context-free canonical no-reminder → pass`);
132
+ // Case 9:context-free canonical reminder(renderer 默认 defaultReviewBy(as_of))→ pass
133
+ // 这是 issue #273 已有的历史形态,最终修复必须恢复。
134
+ const compatTripStart = '2027-08-01';
135
+ const compatTripStartLine = renderPolicyFact(policy, compatTripStart);
136
+ const r9 = await factGate.execute({ markdown: ['## 政策', compatTripStartLine].join('\n') }, compatExec);
137
+ assert.equal(r9.verdict, 'pass', `context-free derived reminder(无 itinerary 桥接)经注册闸应 pass,实际: ${JSON.stringify({ verdict: r9.verdict, summary: r9.summary })}`);
138
+ assert.equal(r9.traceable, 1);
139
+ console.log(` ok - 注册 gotry_fact_gate context-free canonical derived reminder(无 itinerary 桥接)→ pass`);
140
+ // Case 10:context-free canonical reminder(review_by 已知)→ pass
141
+ // reviewByPolicy 已在 Case 2 装载;这里只重复调用同一锚定行,证明无 itinerary 仍合法
142
+ const r10 = await factGate.execute({ markdown: ['## 政策', reviewByLine].join('\n') }, compatExec);
143
+ assert.equal(r10.verdict, 'pass', `context-free canonical reminder(review_by 已知)经注册闸应 pass,实际: ${JSON.stringify({ verdict: r10.verdict, summary: r10.summary })}`);
144
+ assert.equal(r10.traceable, 1);
145
+ console.log(` ok - 注册 gotry_fact_gate context-free canonical reminder(review_by 已知)→ pass`);
146
+ // Case 11:context-free reminder 错位(把 reminder 段追加到 provenance 之后,而不是
147
+ // renderer 固定位置 `[provenance]` 之前)→ 重建 `renderPolicyFact({...f, review_by: '1999-01-01'})`
148
+ // 与原行不匹配 → fail-closed。日期 '1999-01-01' 本身合法 ISO 阳历日;此例证伪在于
149
+ // reminder 位置错配,不在日期本身。
150
+ const compatBadDateLine = `${canonicalLine};远期政策须复核——到 1999-01-01 再核验一次 <!-- fact:${policy.fact_id} -->`;
151
+ const r11 = await factGate.execute({ markdown: ['## 政策', compatBadDateLine].join('\n') }, compatExec);
152
+ assert.equal(r11.verdict, 'blocked', `reminder 错位经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r11.verdict, summary: r11.summary })}`);
153
+ assert.ok(r11.violations?.some(v => v.kind === 'fact_anchor_unknown'), `reminder 错位应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r11.violations?.map(v => v.kind))}`);
154
+ console.log(` ok - 注册 gotry_fact_gate context-free reminder 错位 → blocked/fact_anchor_unknown`);
155
+ // Case 12:context-free body 篡改(免签停留 → 不免签停留,保留 fact_id 锚点)→ blocked
156
+ const compatBadBodyLine = canonicalLine.replace('免签停留', '不免签停留');
157
+ const r12 = await factGate.execute({ markdown: ['## 政策', compatBadBodyLine].join('\n') }, compatExec);
158
+ assert.equal(r12.verdict, 'blocked', `body 篡改经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r12.verdict, summary: r12.summary })}`);
159
+ assert.ok(r12.violations?.some(v => v.kind === 'fact_anchor_unknown'), `body 篡改应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r12.violations?.map(v => v.kind))}`);
160
+ console.log(` ok - 注册 gotry_fact_gate context-free body 篡改 → blocked/fact_anchor_unknown`);
161
+ // Case 13:context-free review_by 篡改(事实带 review_by,改 reminder 日期)→ blocked
162
+ // 根 contract 第 2 条:若事实自带 review_by,renderer 优先级 review_by > tripStart,
163
+ // reminder 日期必须 == f.review_by;改了就是伪造。这里注册的是带 review_by 的事实。
164
+ const fReviewBy = { ...policy, fact_id: makeFactId(['policy-359', 'reg', 'review-by-tamper']), review_by: '2027-06-16' };
165
+ await appendFacts(stateRoot, [fReviewBy]);
166
+ const compatReviewByTamperedLine = renderPolicyFact(fReviewBy).replace('2027-06-16', '2027-12-31');
167
+ const r13 = await factGate.execute({ markdown: ['## 政策', compatReviewByTamperedLine].join('\n') }, compatExec);
168
+ assert.equal(r13.verdict, 'blocked', `事实带 review_by 时改 reminder 日期经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r13.verdict, summary: r13.summary })}`);
169
+ assert.ok(r13.violations?.some(v => v.kind === 'fact_anchor_unknown'), `事实带 review_by 时改 reminder 日期应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r13.violations?.map(v => v.kind))}`);
170
+ console.log(` ok - 注册 gotry_fact_gate 事实带 review_by 改 reminder 日期 → blocked/fact_anchor_unknown`);
171
+ // Case 14:context-free trailing-after-anchor(锚点后追加非空)→ blocked
172
+ const compatTrailingLine = `${canonicalLine} 反而是落地签`;
173
+ const r14 = await factGate.execute({ markdown: ['## 政策', compatTrailingLine].join('\n') }, compatExec);
174
+ assert.equal(r14.verdict, 'blocked', `trailing-after-anchor 经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r14.verdict, summary: r14.summary })}`);
175
+ assert.ok(r14.violations?.some(v => v.kind === 'fact_anchor_unknown'), `trailing-after-anchor 应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r14.violations?.map(v => v.kind))}`);
176
+ console.log(` ok - 注册 gotry_fact_gate context-free trailing-after-anchor → blocked/fact_anchor_unknown`);
177
+ // Case 15:itinerary.trip_start 与 renderer reminder 不一致 → blocked
178
+ // 闸侧走 itinerary 派发后不再回退兼容,renderer 给的是 2027-08-01 而 itinerary
179
+ // 给的是 2027-08-15 → 必 fail-closed。
180
+ const r15 = await factGate.execute({
181
+ markdown: ['## 政策', compatTripStartLine].join('\n'),
182
+ itinerary: {
183
+ trip_start: '2027-08-15',
184
+ trip_end: '2027-08-15',
185
+ stays: [],
186
+ onboard_nights: 0,
187
+ od_segments: [],
188
+ budget_items: [],
189
+ },
190
+ }, compatExec);
191
+ assert.equal(r15.verdict, 'blocked', `itinerary 与 reminder 不一致经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r15.verdict, summary: r15.summary })}`);
192
+ assert.ok(r15.violations?.some(v => v.kind === 'fact_anchor_unknown'), `itinerary 与 reminder 不一致应有 fact_anchor_unknown 违例,实际: ${JSON.stringify(r15.violations?.map(v => v.kind))}`);
193
+ console.log(` ok - 注册 gotry_fact_gate itinerary 与 reminder 不一致 → blocked/fact_anchor_unknown`);
194
+ // Case 16:itinerary.trip_start 存在时 canonical no-reminder 仍合法 → pass
195
+ // renderer 没接 tripStart 时,policy 文本里没有 reminder 段,与 itinerary 共存
196
+ // 仍是合法的 canonical body。
197
+ const r16 = await factGate.execute({
198
+ markdown: ['## 政策', canonicalLine].join('\n'),
199
+ itinerary: {
200
+ trip_start: '2027-08-15',
201
+ trip_end: '2027-08-15',
202
+ stays: [],
203
+ onboard_nights: 0,
204
+ od_segments: [],
205
+ budget_items: [],
206
+ },
207
+ }, compatExec);
208
+ assert.equal(r16.verdict, 'pass', `itinerary 存在时 canonical no-reminder 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r16.verdict, summary: r16.summary })}`);
209
+ assert.equal(r16.traceable, 1);
210
+ console.log(` ok - 注册 gotry_fact_gate itinerary 存在时 canonical no-reminder → pass`);
211
+ rmSync(home, { recursive: true, force: true });
212
+ console.log('\nPOLICY ANCHOR #359 REGISTERED E2E: pass (deterministic synthetic, isolated stateRoot, registered gotry_fact_gate execute())');
213
+ }
214
+ await main();
@@ -0,0 +1,340 @@
1
+ /**
2
+ * 价格漂移监测合同测试(issue #49 长效机制,run-all §41)。
3
+ * 全确定性、零网络:
4
+ * - diff 算法守门:baseline 改/新/删均被检出;
5
+ * - offline 模式:无 baseline → skipped_no_baseline;有 baseline → unchanged(reason 提示 --fetch);
6
+ * - fetch 模式:fetch 失败 → skipped_no_network;parse 失败 → skipped_parse_error;baseline 不存在 →
7
+ * fetched_no_table_entry 且写 fixture;baseline 存在 + 完全相同 → unchanged;存在 + 不同 → drift;
8
+ * - 写入 baseline fixture 的 schema_version 守门;
9
+ * - PR 段落渲染:drift 含 model/field/from/to/direction 表格 + 纪律注脚。
10
+ * - diff 细节:字段 up/down/new/removed 四个方向全检。
11
+ *
12
+ * 网络例一律走注入的 fetcher / providers 路径,不真联网(真联网在 founder 手动 / CI 心跳)。
13
+ */
14
+ import assert from 'node:assert/strict';
15
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { tmpdir } from 'node:os';
18
+ import { runPriceDriftWatch, parseOpenaiHtml, } from './price-drift-watch.js';
19
+ let passed = 0;
20
+ function ok(label) {
21
+ passed += 1;
22
+ console.log(` ok ${label}`);
23
+ }
24
+ function tmpRoot() {
25
+ return mkdtempSync(join(tmpdir(), 'drift-'));
26
+ }
27
+ function makeBaseline(rows, asOf = '2026-08-30T00:00:00.000Z') {
28
+ return JSON.stringify({ schema_version: 'gotry_llm_price_baseline_v1', as_of: asOf, source_url: 'mock://test', rows });
29
+ }
30
+ /** 测试用注入 provider:baseline_path 指向 tmp 文件,parser 接受 mock HTML 字符串。 */
31
+ function makeMockProvider(id, baselinePath, _fetchReturn) {
32
+ const parser = (html) => {
33
+ const rows = [];
34
+ for (const seg of html.split(';')) {
35
+ const [model, nums] = seg.split(':');
36
+ if (!model || !nums)
37
+ continue;
38
+ const [miss, hit, out] = nums.split(',').map(Number);
39
+ if (!Number.isFinite(miss) || !Number.isFinite(out))
40
+ continue;
41
+ rows.push({ model: model.trim(), input_cache_miss_usd_per_1m: miss, input_cache_hit_usd_per_1m: Number.isFinite(hit) ? hit : null, output_usd_per_1m: out });
42
+ }
43
+ return rows;
44
+ };
45
+ return [{
46
+ id,
47
+ source_url: `mock://${id}`,
48
+ baseline_path: baselinePath,
49
+ parse: parser,
50
+ }];
51
+ }
52
+ function fetcherFromMap(map) {
53
+ return async (url) => {
54
+ const v = map[url];
55
+ if (v instanceof Error)
56
+ throw v;
57
+ if (typeof v === 'string')
58
+ return v;
59
+ throw new Error(`no mock for ${url}`);
60
+ };
61
+ }
62
+ // ---- 1. offline 无 baseline → skipped_no_baseline ----
63
+ async function testOfflineNoBaseline() {
64
+ const root = tmpRoot();
65
+ try {
66
+ const report = await runPriceDriftWatch({
67
+ mode: 'offline',
68
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
69
+ providers: makeMockProvider('mockprov', join(root, 'no-such-file.json'), ''),
70
+ });
71
+ assert.equal(report.schema_version, 'gotry_llm_price_drift_v1');
72
+ assert.equal(report.providers['mockprov'].status, 'skipped_no_baseline');
73
+ ok('offline 无 baseline → skipped_no_baseline');
74
+ }
75
+ finally {
76
+ rmSync(root, { recursive: true, force: true });
77
+ }
78
+ }
79
+ // ---- 2. offline 有 baseline → unchanged(reason 提示 --fetch) ----
80
+ async function testOfflineWithBaseline() {
81
+ const root = tmpRoot();
82
+ try {
83
+ const bp = join(root, 'b.json');
84
+ writeFileSync(bp, makeBaseline([
85
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
86
+ ]));
87
+ const report = await runPriceDriftWatch({
88
+ mode: 'offline',
89
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
90
+ providers: makeMockProvider('mockprov', bp, ''),
91
+ });
92
+ assert.equal(report.providers['mockprov'].status, 'unchanged');
93
+ assert.match(report.providers['mockprov'].reason ?? '', /--fetch/, 'offline reason 应提示 --fetch');
94
+ ok('offline 有 baseline → unchanged + reason 提示 --fetch');
95
+ }
96
+ finally {
97
+ rmSync(root, { recursive: true, force: true });
98
+ }
99
+ }
100
+ // ---- 3. fetch 模式:fet 失败 → skipped_no_network ----
101
+ async function testFetchFailure() {
102
+ const root = tmpRoot();
103
+ try {
104
+ const bp = join(root, 'b.json');
105
+ writeFileSync(bp, makeBaseline([
106
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
107
+ ]));
108
+ const report = await runPriceDriftWatch({
109
+ mode: 'fetch',
110
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
111
+ providers: makeMockProvider('mockprov', bp, ''),
112
+ fetcher: fetcherFromMap({ 'mock://mockprov': new Error('ECONNREFUSED') }),
113
+ });
114
+ assert.equal(report.providers['mockprov'].status, 'skipped_no_network');
115
+ assert.match(report.providers['mockprov'].reason ?? '', /ECONNREFUSED/);
116
+ ok('fetch 失败 → skipped_no_network');
117
+ }
118
+ finally {
119
+ rmSync(root, { recursive: true, force: true });
120
+ }
121
+ }
122
+ // ---- 4. fetch 模式:首次 fetch 无 baseline → fetched_no_table_entry 且写 fixture ----
123
+ async function testFetchFirstTimeWritesBaseline() {
124
+ const root = tmpRoot();
125
+ try {
126
+ const bp = join(root, 'b.json');
127
+ const report = await runPriceDriftWatch({
128
+ mode: 'fetch',
129
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
130
+ providers: makeMockProvider('mockprov', bp, ''),
131
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'm1:1.0,0.1,3.0;m2:2.0,0.2,5.0' }),
132
+ });
133
+ assert.equal(report.providers['mockprov'].status, 'fetched_no_table_entry');
134
+ // 必须有 fixture 文件
135
+ const { existsSync, readFileSync } = await import('node:fs');
136
+ assert.ok(existsSync(bp), 'baseline fixture 必须被写');
137
+ const written = JSON.parse(readFileSync(bp, 'utf8'));
138
+ assert.equal(written['schema_version'], 'gotry_llm_price_baseline_v1');
139
+ assert.ok(Array.isArray(written['rows']));
140
+ assert.equal(written['rows'].length, 2);
141
+ ok('fetch 首次 → fetched_no_table_entry + 写 fixture');
142
+ }
143
+ finally {
144
+ rmSync(root, { recursive: true, force: true });
145
+ }
146
+ }
147
+ // ---- 5. fetch 模式:baseline + current 完全相同 → unchanged ----
148
+ async function testFetchUnchanged() {
149
+ const root = tmpRoot();
150
+ try {
151
+ const bp = join(root, 'b.json');
152
+ writeFileSync(bp, makeBaseline([
153
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
154
+ ]));
155
+ const report = await runPriceDriftWatch({
156
+ mode: 'fetch',
157
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
158
+ providers: makeMockProvider('mockprov', bp, ''),
159
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'm1:1.0,0.1,3.0' }),
160
+ });
161
+ assert.equal(report.providers['mockprov'].status, 'unchanged');
162
+ ok('fetch 模式:baseline + current 同 → unchanged');
163
+ }
164
+ finally {
165
+ rmSync(root, { recursive: true, force: true });
166
+ }
167
+ }
168
+ // ---- 6. fetch 模式:baseline + current 不同 → drift + 字段方向 ----
169
+ async function testFetchDrift() {
170
+ const root = tmpRoot();
171
+ try {
172
+ const bp = join(root, 'b.json');
173
+ writeFileSync(bp, makeBaseline([
174
+ { model: 'm-keep', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
175
+ { model: 'm-up', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
176
+ { model: 'm-down', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
177
+ { model: 'm-removed', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
178
+ ]));
179
+ const report = await runPriceDriftWatch({
180
+ mode: 'fetch',
181
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
182
+ providers: makeMockProvider('mockprov', bp, ''),
183
+ fetcher: fetcherFromMap({
184
+ 'mock://mockprov': [
185
+ 'm-keep:1.0,0.1,3.0',
186
+ 'm-up:1.5,0.1,3.0', // miss 1.0→1.5 up
187
+ 'm-down:0.8,0.1,3.0', // miss 1.0→0.8 down
188
+ 'm-new:2.0,0.2,4.0', // 全 new
189
+ // m-removed 不在 current,标记 removed
190
+ ].join(';'),
191
+ }),
192
+ });
193
+ const r = report.providers['mockprov'];
194
+ assert.equal(r.status, 'drift');
195
+ const drift = r.drift ?? [];
196
+ // 期望:m-up input_cache_miss up, m-down input_cache_miss down, m-new input_cache_miss new, m-removed input_cache_miss removed
197
+ const fields = drift.map(d => `${d.model}|${d.field}|${d.direction}`).sort();
198
+ assert.deepEqual(fields, [
199
+ 'm-down|input_cache_miss_usd_per_1m|down',
200
+ 'm-new|input_cache_miss_usd_per_1m|new',
201
+ 'm-removed|input_cache_miss_usd_per_1m|removed',
202
+ 'm-up|input_cache_miss_usd_per_1m|up',
203
+ ], 'drift 字段方向必须逐项正确');
204
+ ok('fetch drift:up/down/new/removed 四向全检');
205
+ }
206
+ finally {
207
+ rmSync(root, { recursive: true, force: true });
208
+ }
209
+ }
210
+ // ---- 7. PR 段落渲染:drift 时含表格 + 纪律注脚 ----
211
+ async function testPrParagraph() {
212
+ const root = tmpRoot();
213
+ try {
214
+ const bp = join(root, 'b.json');
215
+ writeFileSync(bp, makeBaseline([
216
+ { model: 'deepseek-v4-flash', input_cache_miss_usd_per_1m: 1.32, input_cache_hit_usd_per_1m: 0.014, output_usd_per_1m: 3.0 },
217
+ ]));
218
+ const report = await runPriceDriftWatch({
219
+ mode: 'fetch',
220
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
221
+ providers: makeMockProvider('mockprov', bp, ''),
222
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'deepseek-v4-flash:1.32,0.014,3.0' }),
223
+ });
224
+ assert.equal(report.providers['mockprov'].status, 'unchanged');
225
+ assert.match(report.suggested_pr_paragraph, /价格漂移监测/);
226
+ assert.match(report.suggested_pr_paragraph, /mockprov/);
227
+ assert.match(report.suggested_pr_paragraph, /人工 PR/);
228
+ ok('PR 段落含监测标题 + provider 变动 + 纪律注脚');
229
+ }
230
+ finally {
231
+ rmSync(root, { recursive: true, force: true });
232
+ }
233
+ }
234
+ // ---- 8. baseline schema_version 守门:不匹配 schema 视为不存在(loader 容错) ----
235
+ async function testBadBaseline() {
236
+ const root = tmpRoot();
237
+ try {
238
+ const bp = join(root, 'b.json');
239
+ writeFileSync(bp, JSON.stringify({ schema_version: 'gotry_llm_price_baseline_v99', rows: [] }));
240
+ // 离线模式:坏 baseline 视为不存在 → skipped_no_baseline
241
+ const report = await runPriceDriftWatch({
242
+ mode: 'offline',
243
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
244
+ providers: makeMockProvider('mockprov', bp, ''),
245
+ });
246
+ assert.equal(report.providers['mockprov'].status, 'skipped_no_baseline', '坏 baseline schema_version 视为不存在,offline 标 SKIP');
247
+ ok('坏 baseline schema_version → offline 视为不存在(SKIP)');
248
+ }
249
+ finally {
250
+ rmSync(root, { recursive: true, force: true });
251
+ }
252
+ }
253
+ await testOfflineNoBaseline();
254
+ await testOfflineWithBaseline();
255
+ await testFetchFailure();
256
+ await testFetchFirstTimeWritesBaseline();
257
+ await testFetchUnchanged();
258
+ await testFetchDrift();
259
+ await testPrParagraph();
260
+ await testBadBaseline();
261
+ // ---- 9. snapshot 路径:parser 解析 __NEXT_DATA__ 内嵌 JSON dump,fallback 解析段落文字 ----
262
+ async function testSnapshotNextDataParser() {
263
+ const root = tmpRoot();
264
+ try {
265
+ const bp = join(root, 'b.json');
266
+ const sp = join(root, 'snapshot.html');
267
+ writeFileSync(bp, makeBaseline([
268
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
269
+ ]));
270
+ // 真实 snapshot 文件模拟 __NEXT_DATA__ 形态
271
+ const html = `<html><script id="__NEXT_DATA__" type="application/json">${JSON.stringify({
272
+ props: { pageProps: { models: [
273
+ { name: 'gpt-4o', input_cost_per_token: 0.0000025, output_cost_per_token: 0.00001, cached_input_cost_per_token: 0.00000125 },
274
+ ] } },
275
+ })}</script></html>`;
276
+ writeFileSync(sp, html);
277
+ const report = await runPriceDriftWatch({
278
+ mode: 'offline',
279
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
280
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: sp, parse: parseOpenaiHtml }],
281
+ });
282
+ assert.equal(report.providers['mockprov'].status, 'unchanged', 'snapshot 与 baseline 同价 → unchanged(offline)');
283
+ ok('snapshot 路径:__NEXT_DATA__ 内嵌 JSON dump 解析成功 + 与 baseline 同 → unchanged');
284
+ }
285
+ finally {
286
+ rmSync(root, { recursive: true, force: true });
287
+ }
288
+ }
289
+ // ---- 10. snapshot 路径:parser fallback 段落文字;baseline 不一致 → drift ----
290
+ async function testSnapshotFallbackDrift() {
291
+ const root = tmpRoot();
292
+ try {
293
+ const bp = join(root, 'b.json');
294
+ const sp = join(root, 'snapshot.html');
295
+ writeFileSync(bp, makeBaseline([
296
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
297
+ ]));
298
+ // __NEXT_DATA__ 解析失败(空 props),parser fallback 走段落文字
299
+ const html = '<html><body><p>gpt-4o … $5.00 / 1M input … $20.00 / 1M output</p></body></html>';
300
+ writeFileSync(sp, html);
301
+ const report = await runPriceDriftWatch({
302
+ mode: 'offline',
303
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
304
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: sp, parse: parseOpenaiHtml }],
305
+ });
306
+ const r = report.providers['mockprov'];
307
+ assert.equal(r.status, 'drift', 'snapshot fallback 解析 + baseline 不一致 → drift');
308
+ const drift = r.drift ?? [];
309
+ assert.ok(drift.some(d => d.field === 'input_cache_miss_usd_per_1m' && d.from === 2.5 && d.to === 5.0 && d.direction === 'up'), 'miss 字段 up drift');
310
+ assert.ok(drift.some(d => d.field === 'output_usd_per_1m' && d.from === 10.0 && d.to === 20.0 && d.direction === 'up'), 'output 字段 up drift');
311
+ ok('snapshot fallback:段落文字解析 + 与 baseline 不同 → drift(含 up/down/new/removed)');
312
+ }
313
+ finally {
314
+ rmSync(root, { recursive: true, force: true });
315
+ }
316
+ }
317
+ // ---- 11. snapshot 缺失守门:有 baseline 但无 snapshot → skipped_no_snapshot ----
318
+ async function testSnapshotMissing() {
319
+ const root = tmpRoot();
320
+ try {
321
+ const bp = join(root, 'b.json');
322
+ writeFileSync(bp, makeBaseline([
323
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
324
+ ]));
325
+ const report = await runPriceDriftWatch({
326
+ mode: 'offline',
327
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
328
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: join(root, 'no-such-snapshot.html'), parse: parseOpenaiHtml }],
329
+ });
330
+ assert.equal(report.providers['mockprov'].status, 'skipped_no_snapshot', 'snapshot 缺失 → SKIP');
331
+ ok('snapshot 缺失 → skipped_no_snapshot(offline)');
332
+ }
333
+ finally {
334
+ rmSync(root, { recursive: true, force: true });
335
+ }
336
+ }
337
+ await testSnapshotNextDataParser();
338
+ await testSnapshotFallbackDrift();
339
+ await testSnapshotMissing();
340
+ console.log(`price-drift-watch tests: ${passed} 组断言全绿(offline,fetch 模式手动/CI 心跳)`);