@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,193 @@
1
+ /**
2
+ * G5 内部差旅桥机械闸(issue #348;governance/复用矩阵纪律的机械化,不是新政策)。
3
+ *
4
+ * 背景:G5 = 「内部差旅工具桥(T 系统侧,脱敏)」的创始人审批门,当前关闭
5
+ * (docs/gotry-master-outline.md 决策门表 + #348)。复用矩阵纪律:内部资产只能
6
+ * bridge/reference,桥接前必须有创始人显式授权。本脚本把该约束落成机械检查:
7
+ * 1. 扫描全部 git-tracked 文件(排除第三方/生成物/创始人私有 state)中
8
+ * 「G5 桥接引用」形状的标识符/配置/文案;
9
+ * 2. 命中时要求 docs/g5-authorization-ledger.md 存在覆盖该路径的 GRANT 条目
10
+ * (含 issue/决策链接);没有 → exit 1,输出可操作指引。
11
+ *
12
+ * 边界(诚实声明):本闸只证明「无授权记录 → 桥接引用不落地」;
13
+ * GRANT 条目只能由创始人侧维护,agent 自助授权不被本闸承认(作者身份靠
14
+ * git review,机械不可证)。真实 G5 授权本身仍是创始人决策(#348)。
15
+ *
16
+ * 命名面豁免(NAMING_SURFACES):门定义/授权台账/guard 自身必须能「提到」
17
+ * 这条关闭中的 lane,否则 tracker 无法描述自己的门。除此之外一律要求 GRANT。
18
+ *
19
+ * 用法:cd ts && npx tsx scripts/g5-guard.ts [--root <dir>]
20
+ * 退出码:0 = 通过;1 = 存在未授权引用;2 = 闸自身无法运行(缺台账/git 失败)。
21
+ * 自测:cd ts && npx tsx scripts/g5-guard-tests.ts(全离线,临时 fixture)。
22
+ */
23
+ import { spawnSync } from 'node:child_process';
24
+ import { existsSync, readFileSync } from 'node:fs';
25
+ import { dirname, join, resolve } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ export const LEDGER_REL = 'docs/g5-authorization-ledger.md';
28
+ export const ISSUE_348_URL = 'https://github.com/Danceiny/gotry/issues/348';
29
+ /**
30
+ * 「G5 桥接引用」的机械形状:标识符/配置键/插件名/中英文邻接短语。
31
+ * 边界用显式 lookaround 而非 `\b`:下划线是 word char,`\b` 会漏掉
32
+ * snake_case(G5_BRIDGE_*)与 camelCase(G5Bridge)相邻形状。
33
+ */
34
+ export const G5_BRIDGE_PATTERNS = [
35
+ { name: 'g5-adjacent-bridge-identifier', regex: /(?<![a-z0-9])g5[\s_-]{0,2}(bridge|bridged|bridging|adapter|adaptor|mcp)/i },
36
+ { name: 'bridge-adjacent-g5-identifier', regex: /(?<![a-z0-9])(bridge|bridged|bridging|adapter|adaptor)[\s_-]{0,2}g5(?![0-9])/i },
37
+ { name: 'internal-travel-mcp-plugin-id', regex: /\bcapability-travel-mcp\b/i },
38
+ { name: 't-system-bridge-identifier', regex: /(?<![a-z0-9])t[-_ ]?system[\s_-]{0,2}(bridge|bridged|bridging|adapter|adaptor|mcp)/i },
39
+ { name: 'bridge-adjacent-t-system', regex: /(?<![a-z0-9])(bridge|bridged|bridging|adapter|adaptor)[\s_-]{0,2}t[-_ ]?system/i },
40
+ { name: 'chinese-g5-bridge-adjacency', regex: /g5\s*桥|桥\s*g5/i },
41
+ { name: 'chinese-t-system-bridge-adjacency', regex: /T\s*系统\s*(桥|适配|接入)|(桥接|接入)\s*T\s*系统/ },
42
+ ];
43
+ /**
44
+ * 命名面豁免:这些文件被允许「提到」G5 lane(定义它/授权它/检测它),
45
+ * 它们不是授权——真实桥接落在其它任何文件都仍需台账 GRANT。
46
+ * - master-outline 对:G5 门定义 + 复用矩阵 T 系统行(tracker 本体);
47
+ * - 授权台账对:授权面自身;
48
+ * - guard + 自测:检测器必须含有自己的检测串;
49
+ * - CHANGELOG.md:由 ts/scripts/build-changelog.ts 从 commit 标题**原样镜像**生成(#621)——描述这条闸的提交
50
+ * 标题一旦进入某个版本段,生成物就会「提到」它。豁免它不削弱闸:真正的桥接代码/配置落在别的文件里,
51
+ * 仍被逐文件扫描(g5-guard-tests 钉死豁免只作用于这一个路径)。
52
+ */
53
+ export const NAMING_SURFACES = new Set([
54
+ 'docs/gotry-master-outline.md',
55
+ 'docs/gotry-master-outline.zh-CN.md',
56
+ 'docs/g5-authorization-ledger.md',
57
+ 'docs/g5-authorization-ledger.zh-CN.md',
58
+ 'ts/scripts/g5-guard.ts',
59
+ 'ts/scripts/g5-guard-tests.ts',
60
+ 'CHANGELOG.md',
61
+ ]);
62
+ /** 排除段:第三方闭包/生成物/创始人私有产品数据(只读红线目录也不扫)。 */
63
+ const EXCLUDED_SEGMENTS = ['node_modules', 'vendor', 'dist', '.git', 'dsh-runtime', 'gotry-state'];
64
+ const LOCKFILE_BASENAMES = new Set(['package-lock.json', 'pnpm-lock.yaml', 'uv.lock', 'bun.lock', 'bun.lockb', 'npm-shrinkwrap.json']);
65
+ const BINARY_EXTENSIONS = new Set([
66
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.woff', '.woff2', '.ttf', '.otf', '.eot',
67
+ '.mp4', '.mov', '.zip', '.gz', '.tgz', '.bz2', '.7z', '.pdf', '.wasm', '.node', '.dylib', '.so', '.bin', '.icns',
68
+ ]);
69
+ export function isExcludedPath(relPath) {
70
+ const segments = relPath.split('/');
71
+ if (segments.some((s) => EXCLUDED_SEGMENTS.includes(s)))
72
+ return true;
73
+ const base = segments[segments.length - 1];
74
+ if (LOCKFILE_BASENAMES.has(base))
75
+ return true;
76
+ const dot = base.lastIndexOf('.');
77
+ if (dot >= 0 && BINARY_EXTENSIONS.has(base.slice(dot).toLowerCase()))
78
+ return true;
79
+ return false;
80
+ }
81
+ /** 单文件内容扫描:返回全部命中行(文件级,供 collectHits 逐文件调用)。 */
82
+ export function scanContent(relPath, content) {
83
+ const hits = [];
84
+ const lines = content.split('\n');
85
+ for (let i = 0; i < lines.length; i += 1) {
86
+ const line = lines[i];
87
+ for (const { name, regex } of G5_BRIDGE_PATTERNS) {
88
+ if (regex.test(line)) {
89
+ hits.push({ path: relPath, line: i + 1, pattern: name, text: line.trim().slice(0, 160) });
90
+ break; // 一行一次足矣,避免同 pattern 行内重复刷屏
91
+ }
92
+ }
93
+ }
94
+ return hits;
95
+ }
96
+ /** 台账 GRANT 行:`- GRANT: <glob> — <说明,必须含 issue/决策链接>`;code fence 内的行(模板/示例)不算数。 */
97
+ export function parseGrants(ledgerText) {
98
+ const grants = [];
99
+ let malformed = 0;
100
+ let inFence = false;
101
+ for (const rawLine of ledgerText.split('\n')) {
102
+ if (/^`{3,}/.test(rawLine.trim())) {
103
+ inFence = !inFence;
104
+ continue;
105
+ }
106
+ if (inFence)
107
+ continue;
108
+ const match = rawLine.match(/^-\s*GRANT:\s*(\S+)\s+—\s+(.+)$/);
109
+ if (!match)
110
+ continue;
111
+ // 无 issue/决策链接的 GRANT 视为不成立(fail closed),单独计数以便报错指引
112
+ if (!/#\d+|https?:\/\//.test(match[2])) {
113
+ malformed += 1;
114
+ continue;
115
+ }
116
+ grants.push({ glob: match[1], description: match[2].trim() });
117
+ }
118
+ return { grants, malformed };
119
+ }
120
+ /** glob → 路径正则:`**` 跨段,`*` 单段内,`?` 单字符。 */
121
+ export function globToRegExp(glob) {
122
+ const escaped = glob
123
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
124
+ .replace(/\*\*/g, '\u0000')
125
+ .replace(/\*/g, '[^/]*')
126
+ .replace(/\?/g, '[^/]')
127
+ .replace(/\u0000/g, '.*');
128
+ return new RegExp(`^${escaped}$`);
129
+ }
130
+ export function grantCovers(grant, relPath) {
131
+ return globToRegExp(grant.glob).test(relPath);
132
+ }
133
+ function listTrackedFiles(root) {
134
+ const result = spawnSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'buffer' });
135
+ if (result.error || result.status !== 0) {
136
+ console.error(`G5 GUARD: 无法枚举 git-tracked 文件(需要 git 仓库): ${result.error?.message ?? result.stderr?.toString().trim()}`);
137
+ process.exit(2);
138
+ }
139
+ return result.stdout.toString().split('\0').filter(Boolean);
140
+ }
141
+ function main() {
142
+ const args = process.argv.slice(2);
143
+ const rootIndex = args.indexOf('--root');
144
+ const root = rootIndex >= 0 ? resolve(args[rootIndex + 1]) : resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
145
+ const ledgerPath = join(root, LEDGER_REL);
146
+ if (!existsSync(ledgerPath)) {
147
+ console.error(`G5 GUARD: 授权台账不存在: ${LEDGER_REL}(闸配置错误,exit 2;台账由创始人侧维护)`);
148
+ process.exit(2);
149
+ }
150
+ const ledger = parseGrants(readFileSync(ledgerPath, 'utf8'));
151
+ const hits = [];
152
+ let scanned = 0;
153
+ for (const relPath of listTrackedFiles(root)) {
154
+ if (isExcludedPath(relPath))
155
+ continue;
156
+ if (NAMING_SURFACES.has(relPath))
157
+ continue;
158
+ const abs = join(root, relPath);
159
+ let content;
160
+ try {
161
+ content = readFileSync(abs, 'utf8');
162
+ }
163
+ catch {
164
+ continue; // 不可读(权限/符号链接悬空):跳过,不属于闸的裁决面
165
+ }
166
+ if (content.includes('\0'))
167
+ continue; // 二进制内容:跳过
168
+ scanned += 1;
169
+ hits.push(...scanContent(relPath, content));
170
+ }
171
+ if (hits.length === 0) {
172
+ console.log(`G5 GUARD: OK — ${scanned} 个 tracked 文件中无未授权 G5 桥引用;台账 GRANT: ${ledger.grants.length}${ledger.malformed ? `(另有 ${ledger.malformed} 条缺 issue/决策链接的 GRANT 被拒)` : ''}`);
173
+ process.exit(0);
174
+ }
175
+ const uncovered = hits.filter((hit) => !ledger.grants.some((grant) => grantCovers(grant, hit.path)));
176
+ for (const hit of hits) {
177
+ const covered = ledger.grants.some((grant) => grantCovers(grant, hit.path));
178
+ console.error(` ${hit.path}:${hit.line} [${hit.pattern}]${covered ? ' (已授权)' : ''} ${hit.text}`);
179
+ }
180
+ if (uncovered.length === 0) {
181
+ console.log(`G5 GUARD: OK — ${hits.length} 处 G5 桥引用均被台账 GRANT 覆盖`);
182
+ process.exit(0);
183
+ }
184
+ console.error(`G5 GUARD: FAIL — ${uncovered.length} 处未授权的 G5 内部差旅桥引用(共扫描 ${scanned} 个文件):`);
185
+ console.error('G5 门当前关闭:桥接内部旅行系统(T 系统)前必须获得创始人显式授权,可执行其一:');
186
+ console.error(` 1. 由创始人在 ${LEDGER_REL} 增加覆盖上述路径的 GRANT 条目(含 issue/决策链接;仅创始人侧维护,agent 不得自助授权);`);
187
+ console.error(' 2. 或移除引用,保持外部供应商路径(公共 MIT hotelbyte-cli 进程桥)。');
188
+ console.error(`Tracker: ${ISSUE_348_URL}(复用矩阵:内部资产只能 bridge/reference,禁止代码/schema 搬运)`);
189
+ process.exit(1);
190
+ }
191
+ const isDirectRun = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
192
+ if (isDirectRun)
193
+ main();
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 春节锚点表生成器(issue #274,D-9 清偿:手抄常量表已被否决——「换个日子的同款债务」)。
3
+ *
4
+ * 用 lunar-typescript(devDependency,MIT,零传递依赖,不进 runtime bundle)在构建期把
5
+ * 正月初一锚点机械生成到 SPRING_FESTIVAL 表,回写 ../src/time-anchor.ts 的生成块
6
+ * ([gen:lunar-anchors:begin/end] 标记之间);首次运行会把旧手抄表原位迁移为生成块。
7
+ * 扩表/换库 = 重跑本命令;表未变则为无 diff 幂等(生成时间只在表真变时刷新)。
8
+ *
9
+ * 运行(在 ts/ 下):
10
+ * npx tsx scripts/gen-lunar-anchors.ts # 生成并回写(全离线,不访问网络)
11
+ * npx tsx scripts/gen-lunar-anchors.ts --check # 只比对不写:表/库版本漂移即非零退出
12
+ * # (挂 run-all-tests.sh §58 漂移闸)
13
+ *
14
+ * 交叉验证不在这里做:2026-2031 旧表 6 条 + 港天文台 2032-2040 核实 9 条已固化为
15
+ * time-eval §7 的双 oracle 断言(红→绿:生成前 oracle B 必红)。
16
+ */
17
+ import { readFileSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { Lunar } from 'lunar-typescript';
20
+ const TARGET = join(import.meta.dirname, '..', 'src', 'time-anchor.ts');
21
+ const BEGIN = '// [gen:lunar-anchors:begin] 生成块起点——手改会被下次生成覆盖';
22
+ const END = '// [gen:lunar-anchors:end]';
23
+ const START_YEAR = 2026;
24
+ const END_YEAR = 2099;
25
+ /** 正月初一 → 公历 YYYY-MM-DD(lunar-typescript:农历年 y 正月初一 的 Solar)。 */
26
+ function springFestivalOf(lunarYear) {
27
+ return Lunar.fromYmd(lunarYear, 1, 1).getSolar().toYmd();
28
+ }
29
+ function buildTable() {
30
+ const table = {};
31
+ for (let y = START_YEAR; y <= END_YEAR; y++)
32
+ table[y] = springFestivalOf(y);
33
+ return table;
34
+ }
35
+ function libVersion() {
36
+ const pkg = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'node_modules', 'lunar-typescript', 'package.json'), 'utf-8'));
37
+ return pkg.version;
38
+ }
39
+ function renderBlock(table, generatedAt) {
40
+ const entries = Object.keys(table).map(y => ` ${y}: '${table[Number(y)]}',`).join('\n');
41
+ return `/**
42
+ * 春节锚点表(正月初一;仅此节日漂移大,故表驱动;元旦/国庆固定月日按「下一次发生」算)。
43
+ * 本块由构建期生成器机械生成,禁止手改(issue #274:手抄常量表=换个日子的同款债务,已否决)。
44
+ * provenance:lunar-typescript@${libVersion()}(MIT,零传递依赖,devDependency 不进 runtime)· 生成时间 ${generatedAt}
45
+ * 命令:cd ts && npx tsx scripts/gen-lunar-anchors.ts(扩表/换库=重跑;--check=漂移闸,run-all §58)
46
+ * 交叉验证:2026-2031 旧表 6 条 + 港天文台 2032-2040 核实 9 条逐条一致(time-eval §7 双 oracle)。
47
+ * 到期守卫:springFestivalHorizonOk——最晚锚点年份 < 当前年+3 测试即红(临近耗尽先红于静默缺失)。
48
+ */
49
+ ${BEGIN}
50
+ export const SPRING_FESTIVAL: Record<number, string> = {
51
+ ${entries}
52
+ }
53
+ ${END}`;
54
+ }
55
+ /** 提取文件中现有生成块;无标记则匹配旧手抄声明(含其上一行单行 doc 注释)原位迁移。 */
56
+ function extractExisting(src) {
57
+ const b = src.indexOf(BEGIN);
58
+ const e = src.indexOf(END);
59
+ if (b !== -1 && e !== -1) {
60
+ const end = e + END.length;
61
+ return { block: src.slice(b, end), replaceFrom: next => src.slice(0, b) + next + src.slice(end) };
62
+ }
63
+ const legacy = src.match(/(?:\/\*\*[^\n]*\*\/\n)?(?:export )?const SPRING_FESTIVAL: Record<number, string> = \{\n(?: \d{4}: '\d{4}-\d{2}-\d{2}',\n)+\}/);
64
+ if (!legacy)
65
+ throw new Error('未找到 SPRING_FESTIVAL 表(既无生成标记也无手抄声明)');
66
+ const m = legacy[0];
67
+ return { block: null, replaceFrom: next => src.replace(m, next) };
68
+ }
69
+ /** 从块文本解析 年→日期 映射(--check 与幂等判定用,生成时间/库版本不参与比较)。 */
70
+ function parseTable(block) {
71
+ const table = {};
72
+ if (!block)
73
+ return table;
74
+ for (const m of block.matchAll(/(\d{4}): '(\d{4}-\d{2}-\d{2})'/g))
75
+ table[Number(m[1])] = m[2];
76
+ return table;
77
+ }
78
+ function tablesEqual(a, b) {
79
+ const ka = Object.keys(a);
80
+ const kb = Object.keys(b);
81
+ return ka.length === kb.length && ka.every(k => a[Number(k)] === b[Number(k)]);
82
+ }
83
+ const check = process.argv.includes('--check');
84
+ const table = buildTable();
85
+ const src = readFileSync(TARGET, 'utf-8');
86
+ const { block, replaceFrom } = extractExisting(src);
87
+ if (block && tablesEqual(parseTable(block), table)) {
88
+ console.log(`lunar-anchors:表已是最新(${START_YEAR}-${END_YEAR},共 ${Object.keys(table).length} 条),无 diff`);
89
+ process.exit(0);
90
+ }
91
+ const next = renderBlock(table, new Date().toISOString());
92
+ if (check) {
93
+ console.error(`lunar-anchors:漂移!${TARGET} 与生成器输出不一致(表内容或生成块缺失),应重跑:cd ts && npx tsx scripts/gen-lunar-anchors.ts`);
94
+ if (block) {
95
+ const have = parseTable(block);
96
+ const want = table;
97
+ for (let y = Math.min(START_YEAR, ...Object.keys(have).map(Number)); y <= END_YEAR; y++) {
98
+ if (have[y] !== want[y])
99
+ console.error(` ${y}: 文件=${have[y] ?? '(缺)'} 生成=${want[y] ?? '(缺)'}`);
100
+ }
101
+ }
102
+ process.exit(1);
103
+ }
104
+ writeFileSync(TARGET, replaceFrom(next));
105
+ const diffYears = Object.keys(table).filter(y => !block || parseTable(block)[Number(y)] !== table[Number(y)]);
106
+ console.log(`lunar-anchors:已回写 ${TARGET}(覆盖 ${START_YEAR}-${END_YEAR},共 ${Object.keys(table).length} 条;本次新增/变更 ${diffYears.length} 条)`);
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Offline geo-atlas loader contract tests (issue #342, mechanism only —
3
+ * default off, zero product callers).
4
+ *
5
+ * Run: cd ts && npx tsx scripts/geo-atlas-tests.ts
6
+ *
7
+ * Fully offline: driven by a small synthetic fixture GeoJSON written to a
8
+ * temp directory (never the committed build output, never the network). The
9
+ * offline claim itself is executed: globalThis.fetch is replaced by a
10
+ * throwing spy for the whole run and asserted to stay at zero calls.
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import { createHash } from 'node:crypto';
14
+ import { readFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
15
+ import { tmpdir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { GEO_ATLAS_MANIFEST_SCHEMA, loadGeoAtlas, lookupById, lookupByName, normalizeAtlasName, } from '../capabilities/geo-atlas.js';
18
+ function sha256(data) {
19
+ return createHash('sha256').update(Buffer.from(data, 'utf8')).digest('hex');
20
+ }
21
+ const SQUARE = [[[[100.0, 21.0], [102.0, 21.0], [102.0, 25.0], [100.0, 25.0], [100.0, 21.0]]]];
22
+ const OTHER_SQUARE = [[[[11.0, 60.0], [13.0, 60.0], [13.0, 62.0], [11.0, 62.0], [11.0, 60.0]]]];
23
+ function feature(properties, coordinates = SQUARE) {
24
+ return { type: 'Feature', properties, geometry: { type: 'MultiPolygon', coordinates } };
25
+ }
26
+ const ADMIN_0_FEATURES = [
27
+ feature({ id: 'CHN', name: 'China', name_zh: '中华人民共和国', admin: 'China', iso_a2: 'CN', iso_a3: 'CHN' }),
28
+ feature({ id: 'NOR', name: 'Norway', name_zh: '挪威', admin: 'Norway', iso_a2: 'NO', iso_a3: 'NOR' }, OTHER_SQUARE),
29
+ feature({ id: 'TL', name: 'Testland', admin: 'Testland', iso_a2: 'TX' }),
30
+ ];
31
+ const ADMIN_1_FEATURES = [
32
+ feature({ id: 'CN-YN', name: 'Yunnan', name_zh: '云南省', admin: 'China', iso_a2: 'CN', adm0_a3: 'CHN' }),
33
+ feature({ id: 'NO-18', name: 'Nordland', admin: 'Norway', iso_a2: 'NO', adm0_a3: 'NOR' }, OTHER_SQUARE),
34
+ feature({ id: 'TL-NR', name: 'Nordland', admin: 'Testland', iso_a2: 'TX', adm0_a3: 'TL' }),
35
+ ];
36
+ const UPSTREAM = { repository: 'https://example.invalid/natural-earth-vector', tag: 'fixture-tag', commit: 'fixture-commit' };
37
+ const LICENSE = 'Natural Earth — public domain (no attribution required)';
38
+ async function writeFixture(dir, options = {}) {
39
+ const versionDir = join(dir, 'fixture-v1');
40
+ await mkdir(versionDir, { recursive: true });
41
+ const admin0Text = options.admin0Text ?? JSON.stringify({ type: 'FeatureCollection', features: ADMIN_0_FEATURES });
42
+ const admin1Text = options.admin1Text ?? JSON.stringify({ type: 'FeatureCollection', features: ADMIN_1_FEATURES });
43
+ await writeFile(join(versionDir, 'admin-0.geojson'), admin0Text, 'utf8');
44
+ await writeFile(join(versionDir, 'admin-1.geojson'), admin1Text, 'utf8');
45
+ const manifest = {
46
+ schemaVersion: GEO_ATLAS_MANIFEST_SCHEMA,
47
+ dataset: 'natural-earth',
48
+ version: 'fixture-v1',
49
+ upstream: UPSTREAM,
50
+ license: { name: LICENSE },
51
+ build: { fetchedAt: '2026-10-02T00:00:00.000Z' },
52
+ artifacts: [
53
+ {
54
+ layer: 'admin-0',
55
+ file: 'admin-0.geojson',
56
+ upstreamUrl: 'https://example.invalid/admin-0.geojson',
57
+ upstreamSha256: 'a'.repeat(64),
58
+ recordCount: 3,
59
+ bytes: Buffer.byteLength(admin0Text),
60
+ sha256: sha256(admin0Text),
61
+ },
62
+ {
63
+ layer: 'admin-1',
64
+ file: 'admin-1.geojson',
65
+ upstreamUrl: 'https://example.invalid/admin-1.geojson',
66
+ upstreamSha256: 'b'.repeat(64),
67
+ recordCount: 3,
68
+ bytes: Buffer.byteLength(admin1Text),
69
+ sha256: sha256(admin1Text),
70
+ },
71
+ ],
72
+ };
73
+ await writeFile(join(versionDir, 'manifest.json'), options.manifestText ?? JSON.stringify(manifest, null, 2), 'utf8');
74
+ return versionDir;
75
+ }
76
+ async function loadOk(dir) {
77
+ const result = await loadGeoAtlas(dir);
78
+ assert.equal(result.status, 'ok', `fixture load must succeed, got ${JSON.stringify(result)}`);
79
+ return result.atlas;
80
+ }
81
+ async function loadErr(dir) {
82
+ const result = await loadGeoAtlas(dir);
83
+ assert.equal(result.status, 'error', `fixture load must fail, got ${JSON.stringify(result)}`);
84
+ return result.reason;
85
+ }
86
+ async function withFixture(name, fn) {
87
+ const root = await mkdtemp(join(tmpdir(), name));
88
+ try {
89
+ await fn(await writeFixture(root));
90
+ }
91
+ finally {
92
+ await rm(root, { recursive: true, force: true });
93
+ }
94
+ }
95
+ const tests = [];
96
+ tests.push({
97
+ name: 'green path: SHA-256 verified load, both layers indexed, provenance asserted',
98
+ fn: async () => {
99
+ await withFixture('geo-atlas-green-', async (versionDir) => {
100
+ const atlas = await loadOk(versionDir);
101
+ assert.equal(atlas.version, 'fixture-v1');
102
+ assert.equal(atlas.recordCount(), 6);
103
+ assert.equal(atlas.recordCount('admin-0'), 3);
104
+ assert.equal(atlas.recordCount('admin-1'), 3);
105
+ assert.equal(atlas.snapshot.dataset, 'natural-earth');
106
+ assert.equal(atlas.snapshot.license, LICENSE);
107
+ assert.deepEqual(atlas.snapshot.upstream, UPSTREAM);
108
+ assert.equal(atlas.snapshot.fetchedAt, '2026-10-02T00:00:00.000Z');
109
+ const hit = lookupByName(atlas, 'China');
110
+ assert.equal(hit.status, 'hit');
111
+ if (hit.status !== 'hit')
112
+ return;
113
+ assert.equal(hit.record.id, 'CHN');
114
+ assert.equal(hit.record.nameZh, '中华人民共和国');
115
+ assert.equal(hit.record.geometry.type, 'MultiPolygon');
116
+ assert.ok(hit.record.geometry.coordinates.length >= 1);
117
+ assert.equal(hit.record.provenance.layer, 'admin-0');
118
+ assert.equal(hit.record.provenance.snapshotFile, 'admin-0.geojson');
119
+ assert.equal(hit.record.provenance.upstreamUrl, 'https://example.invalid/admin-0.geojson');
120
+ const onDisk = await readFile(join(versionDir, 'admin-0.geojson'), 'utf8');
121
+ assert.equal(hit.record.provenance.snapshotSha256, sha256(onDisk));
122
+ assert.equal(hit.snapshot.version, 'fixture-v1');
123
+ });
124
+ },
125
+ });
126
+ tests.push({
127
+ name: 'by-name matches the zh name and normalizes case/whitespace',
128
+ fn: async () => {
129
+ await withFixture('geo-atlas-names-', async (versionDir) => {
130
+ const atlas = await loadOk(versionDir);
131
+ const zh = lookupByName(atlas, '中华人民共和国');
132
+ assert.equal(zh.status, 'hit');
133
+ if (zh.status === 'hit')
134
+ assert.equal(zh.record.id, 'CHN');
135
+ const messy = lookupByName(atlas, ' cHina ');
136
+ assert.equal(messy.status, 'hit');
137
+ assert.equal(normalizeAtlasName(' YUNNAN '), 'yunnan');
138
+ });
139
+ },
140
+ });
141
+ tests.push({
142
+ name: 'by-id lookup hits with the layer provenance of that record',
143
+ fn: async () => {
144
+ await withFixture('geo-atlas-id-', async (versionDir) => {
145
+ const atlas = await loadOk(versionDir);
146
+ const hit = lookupById(atlas, 'CN-YN');
147
+ assert.equal(hit.status, 'hit');
148
+ if (hit.status !== 'hit')
149
+ return;
150
+ assert.equal(hit.record.admin, 'China');
151
+ assert.equal(hit.record.provenance.layer, 'admin-1');
152
+ assert.equal(hit.record.provenance.snapshotFile, 'admin-1.geojson');
153
+ assert.equal(hit.record.provenance.snapshotSha256, sha256(await readFile(join(versionDir, 'admin-1.geojson'), 'utf8')));
154
+ const country = lookupById(atlas, 'chn');
155
+ assert.equal(country.status, 'hit');
156
+ });
157
+ },
158
+ });
159
+ tests.push({
160
+ name: 'same-name place → ambiguous with candidates, never a silent pick; layer filter narrows',
161
+ fn: async () => {
162
+ await withFixture('geo-atlas-ambig-', async (versionDir) => {
163
+ const atlas = await loadOk(versionDir);
164
+ const ambiguous = lookupByName(atlas, 'Nordland');
165
+ assert.equal(ambiguous.status, 'ambiguous');
166
+ if (ambiguous.status !== 'ambiguous')
167
+ return;
168
+ assert.equal(ambiguous.candidates.length, 2);
169
+ const ids = ambiguous.candidates.map((c) => c.id).sort();
170
+ assert.deepEqual(ids, ['NO-18', 'TL-NR']);
171
+ for (const candidate of ambiguous.candidates) {
172
+ assert.equal(candidate.layer, 'admin-1');
173
+ assert.ok(candidate.admin);
174
+ }
175
+ assert.match(ambiguous.note, /clarify/i);
176
+ const narrowed = lookupByName(atlas, 'Nordland', { layer: 'admin-0' });
177
+ assert.equal(narrowed.status, 'unknown');
178
+ });
179
+ },
180
+ });
181
+ tests.push({
182
+ name: 'unknown region → three-valued miss, snapshot provenance still attached',
183
+ fn: async () => {
184
+ await withFixture('geo-atlas-unknown-', async (versionDir) => {
185
+ const atlas = await loadOk(versionDir);
186
+ for (const miss of [lookupByName(atlas, 'Atlantis'), lookupById(atlas, 'XX-XX')]) {
187
+ assert.equal(miss.status, 'unknown');
188
+ if (miss.status !== 'unknown')
189
+ continue;
190
+ assert.equal(miss.snapshot.version, 'fixture-v1');
191
+ assert.match(miss.note, /not proof/);
192
+ }
193
+ const empty = lookupByName(atlas, ' ');
194
+ assert.equal(empty.status, 'unknown');
195
+ });
196
+ },
197
+ });
198
+ tests.push({
199
+ name: 'fail-closed: missing/corrupt/invalid manifest, missing/tampered/corrupt/invalid artifact, duplicate id',
200
+ fn: async () => {
201
+ const base = await mkdtemp(join(tmpdir(), 'geo-atlas-fail-'));
202
+ try {
203
+ const missing = await loadGeoAtlas(join(base, 'no-such-dir'));
204
+ assert.equal(missing.status, 'error');
205
+ const rawManifestDir = join(base, 'raw-manifest');
206
+ await mkdir(rawManifestDir, { recursive: true });
207
+ await writeFile(join(rawManifestDir, 'manifest.json'), '{not json', 'utf8');
208
+ assert.equal(await loadErr(rawManifestDir), 'manifest-corrupt');
209
+ const badSchemaDir = await writeFixture(base, {
210
+ manifestText: JSON.stringify({ schemaVersion: 'something-else', dataset: 'x', version: 'y', artifacts: [] }),
211
+ });
212
+ assert.equal(await loadErr(badSchemaDir), 'manifest-invalid');
213
+ const missingArtifactDir = await writeFixture(base);
214
+ await rm(join(missingArtifactDir, 'admin-1.geojson'));
215
+ assert.equal(await loadErr(missingArtifactDir), 'artifact-missing');
216
+ // Tamper happens AFTER the manifest is on disk: the snapshot file no
217
+ // longer matches its recorded SHA-256 (this is the fail-closed seam).
218
+ const tamperedDir = await writeFixture(base);
219
+ await writeFile(join(tamperedDir, 'admin-1.geojson'), await readFile(join(tamperedDir, 'admin-1.geojson'), 'utf8') + ' ', 'utf8');
220
+ assert.equal(await loadErr(tamperedDir), 'artifact-sha-mismatch');
221
+ const brokenJson = JSON.stringify({ type: 'FeatureCollection', features: ADMIN_1_FEATURES }).slice(0, 40);
222
+ const corruptArtifactDir = await writeFixture(base, { admin1Text: brokenJson });
223
+ assert.equal(await loadErr(corruptArtifactDir), 'artifact-corrupt');
224
+ const shapeDriftText = JSON.stringify({
225
+ type: 'FeatureCollection',
226
+ features: [{ type: 'Feature', properties: { id: 'NO-18', name: 'Nordland' } }, ...ADMIN_1_FEATURES.slice(1)],
227
+ });
228
+ const shapeDriftDir = await writeFixture(base, { admin1Text: shapeDriftText });
229
+ assert.equal(await loadErr(shapeDriftDir), 'artifact-invalid');
230
+ const notACollection = JSON.stringify({ type: 'Feature', geometry: null });
231
+ const notCollectionDir = await writeFixture(base, { admin0Text: notACollection });
232
+ assert.equal(await loadErr(notCollectionDir), 'artifact-invalid');
233
+ const duplicateText = JSON.stringify({
234
+ type: 'FeatureCollection',
235
+ features: [...ADMIN_0_FEATURES, feature({ id: 'CHN', name: 'China Duplicate' })],
236
+ });
237
+ const duplicateDir = await writeFixture(base, { admin0Text: duplicateText });
238
+ assert.equal(await loadErr(duplicateDir), 'duplicate-id');
239
+ }
240
+ finally {
241
+ await rm(base, { recursive: true, force: true });
242
+ }
243
+ },
244
+ });
245
+ tests.push({
246
+ name: 'determinism: two loads of the same root answer identically',
247
+ fn: async () => {
248
+ await withFixture('geo-atlas-det-', async (versionDir) => {
249
+ const first = await loadOk(versionDir);
250
+ const second = await loadOk(versionDir);
251
+ assert.deepEqual(first.snapshot, second.snapshot);
252
+ assert.deepEqual(lookupByName(first, 'Yunnan'), lookupByName(second, 'Yunnan'));
253
+ assert.deepEqual(lookupById(first, 'TL-NR'), lookupById(second, 'TL-NR'));
254
+ });
255
+ },
256
+ });
257
+ // The offline spy wraps the entire suite (installed before any test runs and
258
+ // asserted afterwards): zero network is an executed contract, not a claim.
259
+ const realFetch = globalThis.fetch;
260
+ let fetchCalls = 0;
261
+ globalThis.fetch = ((...args) => {
262
+ fetchCalls += 1;
263
+ throw new Error(`geo-atlas loader must be offline; fetch called with ${JSON.stringify(args[0])}`);
264
+ });
265
+ let failed = 0;
266
+ for (const test of tests) {
267
+ try {
268
+ await test.fn();
269
+ console.log(`ok - ${test.name}`);
270
+ }
271
+ catch (error) {
272
+ failed += 1;
273
+ console.error(`FAIL - ${test.name}`);
274
+ console.error(error);
275
+ }
276
+ }
277
+ globalThis.fetch = realFetch;
278
+ assert.equal(fetchCalls, 0, `loader made ${fetchCalls} network call(s); offline contract violated`);
279
+ if (failed > 0) {
280
+ console.error(`${failed} geo-atlas test(s) failed`);
281
+ process.exit(1);
282
+ }
283
+ console.log(`geo-atlas contract tests: ${tests.length} passed, 0 failed (fetch calls: ${fetchCalls})`);