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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -0,0 +1,392 @@
1
+ /**
2
+ * #233 取消退款独立结果与佣金披露 契约层回归(M5-4 pre-entry,run-all §62):
3
+ * 1 独立结果封闭集:cancel 与 refund 是两个对象、两套状态,无合并成功态;导出面全清单冻结(零意外 API);
4
+ * supplierReferenceNo 只能由供应商返回值构造(不解码/不猜测)
5
+ * 2 非对称终态:取消成功(供应商显式取消回执)但退款失败可表达;仅「已提交取消申请」⇒ orderCancelled 仍 unknown(已申请≠已取消)
6
+ * 3 退款超时/未知不升级:refunded 无权威证据即拒;pending/unknown 的 moneyReturned 恒 unknown
7
+ * 4 cancel.serviceFee ≠ 客户退款金额:serviceFee 来源不能当退款金额;到账结论只绑退款/钱包权威证据;手续费只单独披露
8
+ * 5 佣金披露:确认边界缺披露即拒;unknown 不默认 none;known_none 须有 contract_rule 来源;口径(金额基准)不一致即拒;
9
+ * 披露 digest 入指纹(canonical JSON 稳定),口径变化 ⇒ digest mismatch ⇒ 旧 receipt 失效需重确认
10
+ * 6 账本分词与文案分词一致:120 组状态×账本×语言组合 + 3 种披露形态,生成文案零违例;
11
+ * 手写/LLM 漂移文案(谎称已取消/已退款/藏佣金)逐反例被抓
12
+ * 7 纯契约面:模块仅依赖 node:crypto,零网络/子进程/账本写入,无任何供应商调用路径(非运行时激活,default-off)
13
+ * 反例先行(红→绿):本套件先于实现编写;实现见 ts/src/booking-surface/cancel-refund-commission.ts
14
+ * 运行(在 ts/ 下):npx tsx scripts/issue-233-cancel-refund-commission-tests.ts
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import { CANCELLATION_OUTCOME_SCHEMA, CANCELLATION_STATUSES, COMMISSION_AMOUNT_BASES, COMMISSION_BENEFIT_KINDS, COMMISSION_CERTAINTIES, COMMISSION_DISCLOSURE_FINGERPRINT_FIELD, COMMISSION_DISCLOSURE_SCHEMA, COMMISSION_SOURCES, COMMISSION_VIOLATION_CODES, COPY_VIOLATION_CODES, LOCAL_LEDGER_STATUSES, REFUND_AUTHORITY_SOURCES, REFUND_OUTCOME_SCHEMA, REFUND_STATUSES, SERVICE_FEE_SOURCE, SUPPLIER_REFERENCE_RETURN_SOURCES, aftercareFacts, bindDisclosureToFingerprint, cancellationOutcome, computeDisclosureDigest, copyTokenViolations, disclosureForConfirmation, localLedgerCopy, refundOutcome, renderAftercareCopy, supplierReferenceNoFromReturn, validateCommissionDisclosure, } from '../src/booking-surface/cancel-refund-commission.js';
18
+ import * as mod from '../src/booking-surface/cancel-refund-commission.js';
19
+ let pass = 0;
20
+ let fail = 0;
21
+ function assert(cond, msg) {
22
+ if (cond) {
23
+ pass++;
24
+ console.log(` ok - ${msg}`);
25
+ }
26
+ else {
27
+ fail++;
28
+ console.error(` FAIL - ${msg}`);
29
+ }
30
+ }
31
+ function isOk(r) {
32
+ return r.ok === true;
33
+ }
34
+ // ---- 1:独立结果封闭集与导出面冻结 ------------------------------------------------
35
+ assert(CANCELLATION_OUTCOME_SCHEMA === 'gotry.cancellation_outcome.v1' && REFUND_OUTCOME_SCHEMA === 'gotry.refund_outcome.v1', '取消/退款结果对象各持唯一 schema(v1)');
36
+ assert(CANCELLATION_STATUSES.join(',') === 'not_requested,cancel_submitted,cancel_failed,cancel_unknown', '取消状态封闭集:未申请/已提交/失败/未知(四态)');
37
+ assert(REFUND_STATUSES.join(',') === 'not_applicable,refund_pending,refund_failed,refunded,refund_unknown', '退款状态封闭集:不涉及/待到账/失败/已到账/未知(五态)');
38
+ const allStatuses = [...CANCELLATION_STATUSES, ...REFUND_STATUSES];
39
+ assert(!allStatuses.includes('success') && !allStatuses.includes('cancelled'), '无合并成功态:两套状态词表都不含 success/cancelled 单一总成功词');
40
+ assert(new Set(allStatuses).size === allStatuses.length, '取消与退款状态词表互不重叠(两个独立结果,不是一个结果的两个投影)');
41
+ assert(REFUND_AUTHORITY_SOURCES.join(',') === 'supplier_refund_record,wallet_refund_record' && SERVICE_FEE_SOURCE === 'cancel_return_service_fee', '退款金额权威来源闭集=供应商退款记录/钱包退款记录;serviceFee 是另一来源常量');
42
+ assert(SUPPLIER_REFERENCE_RETURN_SOURCES.join(',') === 'supplier_cancel_return,supplier_query_return,supplier_refund_return', 'supplierReferenceNo 只允许三种供应商返回来源(不解码猜测)');
43
+ assert(LOCAL_LEDGER_STATUSES.join(',') === 'pending,confirmed,compensated', 'ADR-17 账本三态只读镜像(本模块不改账本)');
44
+ assert(COMMISSION_VIOLATION_CODES.join(',') === 'commission_undisclosed,commission_none_without_source,commission_benefit_incomplete,commission_amount_basis_inconsistent,disclosure_digest_mismatch', '佣金披露违例码封闭集(含 digest mismatch=口径变化拒绝)');
45
+ assert(COPY_VIOLATION_CODES.join(',') === 'copy_claimed_cancelled_without_supplier_evidence,copy_claimed_refund_arrival_without_authority,copy_refund_wording_while_pending_or_unknown,copy_refund_wording_on_local_compensation_only,commission_disclosure_missing_from_summary,commission_status_contradicts_summary', '文案违例码封闭集(谎称已取消/已到账/pending 措辞/compensated 退款/藏佣金/口径矛盾)');
46
+ const EXPECTED_EXPORTS = [
47
+ 'CANCELLATION_OUTCOME_SCHEMA', 'CANCELLATION_STATUSES',
48
+ 'COMMISSION_AMOUNT_BASES', 'COMMISSION_BENEFIT_KINDS', 'COMMISSION_CERTAINTIES',
49
+ 'COMMISSION_DISCLOSURE_FINGERPRINT_FIELD', 'COMMISSION_DISCLOSURE_SCHEMA',
50
+ 'COMMISSION_SOURCES', 'COMMISSION_VIOLATION_CODES',
51
+ 'COPY_VIOLATION_CODES', 'LOCAL_LEDGER_STATUSES', 'REFUND_AUTHORITY_SOURCES',
52
+ 'REFUND_OUTCOME_SCHEMA', 'REFUND_STATUSES', 'SERVICE_FEE_SOURCE',
53
+ 'SUPPLIER_REFERENCE_RETURN_SOURCES',
54
+ 'aftercareFacts', 'bindDisclosureToFingerprint', 'cancellationOutcome',
55
+ 'computeDisclosureDigest', 'copyTokenViolations', 'disclosureForConfirmation',
56
+ 'localLedgerCopy', 'refundOutcome', 'renderAftercareCopy',
57
+ 'supplierReferenceNoFromReturn', 'validateCommissionDisclosure',
58
+ ].sort();
59
+ assert(JSON.stringify(Object.keys(mod).sort()) === JSON.stringify(EXPECTED_EXPORTS), '导出面全清单冻结(无隐匿的第二 API/无意外副作用出口)');
60
+ {
61
+ const good = supplierReferenceNoFromReturn('SR-881', 'supplier_cancel_return');
62
+ assert(good.ok && good.value.value === 'SR-881', 'supplierReferenceNo 由供应商 cancel 返回值构造成功');
63
+ assert(!supplierReferenceNoFromReturn('', 'supplier_cancel_return').ok && !supplierReferenceNoFromReturn(' ', 'supplier_query_return').ok, '空/空白返回值拒绝(fail-closed)');
64
+ const badSource = supplierReferenceNoFromReturn('SR-881', 'customer_reference_decoded');
65
+ assert(!badSource.ok, '非供应商返回来源(从 customerReferenceNo 解码)构造被拒');
66
+ const cancelOk = cancellationOutcome({
67
+ schema: CANCELLATION_OUTCOME_SCHEMA,
68
+ status: 'cancel_submitted',
69
+ customerReferenceNo: 'GT-20260911-001',
70
+ supplierReferenceNo: isOk(good) ? good.value : undefined,
71
+ observedAt: '2026-09-11T10:00:00.000Z',
72
+ });
73
+ assert(isOk(cancelOk) && Object.isFrozen(cancelOk.value), '合法取消结果对象构造成功且冻结(不可变投影)');
74
+ const badCancel = cancellationOutcome({
75
+ schema: CANCELLATION_OUTCOME_SCHEMA,
76
+ status: 'cancel_submitted',
77
+ customerReferenceNo: 'GT-20260911-001',
78
+ supplierReferenceNo: { value: 'SR-881', source: 'customer_reference_decoded' },
79
+ observedAt: '2026-09-11T10:00:00.000Z',
80
+ });
81
+ assert(!isOk(badCancel) && badCancel.reason === 'supplier_reference_not_returned_value', '取消对象携带非返回值来源的 supplierReferenceNo 被拒');
82
+ const badStatus = cancellationOutcome({
83
+ schema: CANCELLATION_OUTCOME_SCHEMA,
84
+ status: 'cancelled',
85
+ customerReferenceNo: 'GT-20260911-001',
86
+ observedAt: '2026-09-11T10:00:00.000Z',
87
+ });
88
+ assert(!isOk(badStatus), '取消对象不存在 cancelled 终态(只有 cancel_submitted/cancel_failed/cancel_unknown)');
89
+ }
90
+ // ---- 2:非对称终态(取消成功但退款失败可表达) --------------------------------------
91
+ {
92
+ const srn = supplierReferenceNoFromReturn('SR-CXL-1', 'supplier_cancel_return');
93
+ const cancel = cancellationOutcome({
94
+ schema: CANCELLATION_OUTCOME_SCHEMA,
95
+ status: 'cancel_submitted',
96
+ customerReferenceNo: 'GT-20260911-002',
97
+ supplierReferenceNo: isOk(srn) ? srn.value : undefined,
98
+ supplierCancellationConfirmed: { digest: 'sha256:cancel-receipt-digest' },
99
+ observedAt: '2026-09-11T10:00:00.000Z',
100
+ });
101
+ const refund = refundOutcome({
102
+ schema: REFUND_OUTCOME_SCHEMA,
103
+ status: 'refund_failed',
104
+ supplierServiceFee: { amount: '80', currency: 'CNY' },
105
+ reasonCode: 'wallet_recovery_failed',
106
+ observedAt: '2026-09-11T10:05:00.000Z',
107
+ });
108
+ assert(isOk(cancel) && isOk(refund), '取消成功+退款失败的非对称终态两对象均可独立构造');
109
+ if (isOk(cancel) && isOk(refund)) {
110
+ const facts = aftercareFacts(cancel.value, refund.value);
111
+ assert(facts.orderCancelled === true && facts.moneyReturned === false, '非对称终态:订单已取消=true 而钱未退=false(不合并成单一成功态)');
112
+ const copyEn = renderAftercareCopy(cancel.value, refund.value, 'confirmed', undefined, 'en');
113
+ const copyZh = renderAftercareCopy(cancel.value, refund.value, 'confirmed', undefined, 'zh-CN');
114
+ assert(/cancel/i.test(copyEn) && /refund failed/i.test(copyEn), 'EN 文案同时表达取消与退款失败两个维度');
115
+ assert(copyZh.includes('已取消') && copyZh.includes('退款失败'), 'ZH 文案同时表达取消与退款失败(供应商回执凭证在场才允许「已取消」)');
116
+ const cancelRequestedOnly = cancellationOutcome({
117
+ schema: CANCELLATION_OUTCOME_SCHEMA,
118
+ status: 'cancel_submitted',
119
+ customerReferenceNo: 'GT-20260911-003',
120
+ observedAt: '2026-09-11T10:00:00.000Z',
121
+ });
122
+ if (isOk(cancelRequestedOnly)) {
123
+ const facts2 = aftercareFacts(cancelRequestedOnly.value, refund.value);
124
+ assert(facts2.orderCancelled === 'unknown', '仅 cancel_submitted(无供应商取消回执)⇒ orderCancelled 仍 unknown:已申请取消≠已取消');
125
+ }
126
+ }
127
+ }
128
+ // ---- 3:退款超时/未知不升级;refunded 必须有权威证据 -------------------------------
129
+ {
130
+ const noEvidence = refundOutcome({
131
+ schema: REFUND_OUTCOME_SCHEMA,
132
+ status: 'refunded',
133
+ customerRefund: { amount: '100', currency: 'CNY', source: 'supplier_refund_record' },
134
+ observedAt: '2026-09-11T10:00:00.000Z',
135
+ });
136
+ assert(!isOk(noEvidence) && noEvidence.reason === 'refund_claimed_without_evidence', 'refunded 无权威证据(evidence)构造即拒:到账结论必须绑定退款/钱包权威证据');
137
+ const pending = refundOutcome({
138
+ schema: REFUND_OUTCOME_SCHEMA,
139
+ status: 'refund_pending',
140
+ customerRefund: { amount: '100', currency: 'CNY', source: 'supplier_refund_record' },
141
+ observedAt: '2026-09-11T10:00:00.000Z',
142
+ });
143
+ const unknown = refundOutcome({
144
+ schema: REFUND_OUTCOME_SCHEMA,
145
+ status: 'refund_unknown',
146
+ observedAt: '2026-09-11T10:00:00.000Z',
147
+ });
148
+ assert(isOk(pending) && isOk(unknown), '退款 pending/unknown 可构造(超时窗口内合法状态)');
149
+ if (isOk(pending) && isOk(unknown)) {
150
+ const cancel = cancellationOutcome({
151
+ schema: CANCELLATION_OUTCOME_SCHEMA,
152
+ status: 'not_requested',
153
+ customerReferenceNo: 'GT-20260911-004',
154
+ observedAt: '2026-09-11T10:00:00.000Z',
155
+ });
156
+ if (isOk(cancel)) {
157
+ assert(aftercareFacts(cancel.value, pending.value).moneyReturned === 'unknown', 'pending 的 moneyReturned 恒 unknown(不自动升级为已到账)');
158
+ assert(aftercareFacts(cancel.value, unknown.value).moneyReturned === 'unknown', 'unknown 的 moneyReturned 恒 unknown');
159
+ }
160
+ }
161
+ const refunded = refundOutcome({
162
+ schema: REFUND_OUTCOME_SCHEMA,
163
+ status: 'refunded',
164
+ customerRefund: { amount: '100', currency: 'CNY', source: 'wallet_refund_record' },
165
+ evidence: { kind: 'wallet_refund_record', digest: 'sha256:wallet-refund-digest' },
166
+ observedAt: '2026-09-11T10:00:00.000Z',
167
+ });
168
+ assert(isOk(refunded), '绑钱包权威证据的 refunded 合法(到账结论=证据)');
169
+ }
170
+ // ---- 4:cancel.serviceFee ≠ 客户退款金额 -----------------------------------------
171
+ {
172
+ const feeAsRefund = refundOutcome({
173
+ schema: REFUND_OUTCOME_SCHEMA,
174
+ status: 'refunded',
175
+ customerRefund: { amount: '80', currency: 'CNY', source: SERVICE_FEE_SOURCE },
176
+ evidence: { kind: 'supplier_refund_record', digest: 'sha256:x' },
177
+ supplierServiceFee: { amount: '80', currency: 'CNY' },
178
+ observedAt: '2026-09-11T10:00:00.000Z',
179
+ });
180
+ assert(!isOk(feeAsRefund) && feeAsRefund.reason === 'service_fee_used_as_refund_amount', 'cancel.serviceFee 来源金额冒充客户退款金额被拒(serviceFee 不是退款)');
181
+ const feeWithoutAuthority = refundOutcome({
182
+ schema: REFUND_OUTCOME_SCHEMA,
183
+ status: 'refund_pending',
184
+ customerRefund: { amount: '80', currency: 'CNY', source: 'cancel_return_service_fee' },
185
+ observedAt: '2026-09-11T10:00:00.000Z',
186
+ });
187
+ assert(!isOk(feeWithoutAuthority), '任何状态下 serviceFee 来源都不能进 customerRefund');
188
+ const srn = supplierReferenceNoFromReturn('SR-CXL-2', 'supplier_cancel_return');
189
+ const cancel = cancellationOutcome({
190
+ schema: CANCELLATION_OUTCOME_SCHEMA,
191
+ status: 'cancel_submitted',
192
+ customerReferenceNo: 'GT-20260911-005',
193
+ supplierReferenceNo: isOk(srn) ? srn.value : undefined,
194
+ observedAt: '2026-09-11T10:00:00.000Z',
195
+ });
196
+ const refundReal = refundOutcome({
197
+ schema: REFUND_OUTCOME_SCHEMA,
198
+ status: 'refunded',
199
+ customerRefund: { amount: '620', currency: 'CNY', source: 'wallet_refund_record' },
200
+ evidence: { kind: 'wallet_refund_record', digest: 'sha256:wallet-2' },
201
+ supplierServiceFee: { amount: '80', currency: 'CNY' },
202
+ observedAt: '2026-09-11T10:10:00.000Z',
203
+ });
204
+ assert(isOk(cancel) && isOk(refundReal), '真实口径可构造:手续费 80 单独在场,退款 620 绑钱包证据');
205
+ if (isOk(cancel) && isOk(refundReal)) {
206
+ const copyZh = renderAftercareCopy(cancel.value, refundReal.value, 'confirmed', undefined, 'zh-CN');
207
+ const copyEn = renderAftercareCopy(cancel.value, refundReal.value, 'confirmed', undefined, 'en');
208
+ assert(copyZh.includes('620') && copyZh.includes('手续费') && !copyZh.includes('退款已到账:80'), 'ZH 文案:退款金额=620 来自钱包证据;80 只作手续费单独披露');
209
+ assert(/620/.test(copyEn) && /service fee/i.test(copyEn) && !/\b80\b\s*(CNY|¥)?\s*(refund|refunded)/i.test(copyEn), 'EN 文案:80 只以 service fee 出现,不得表述为客户退款');
210
+ }
211
+ }
212
+ // ---- 5:佣金披露(digest 入指纹,口径变化使旧 receipt 失效) ------------------------
213
+ const disclosureOk = {
214
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
215
+ certainty: 'known_benefit',
216
+ benefits: [{ kind: 'commission', paidBy: 'supplier', paidTo: 'gotry', basis: { type: 'percent_of_total', percent: '10' } }],
217
+ source: 'supplier_field',
218
+ };
219
+ {
220
+ assert(COMMISSION_BENEFIT_KINDS.join(',') === 'commission,rebate,sponsorship,preferred_placement', '受益类型封闭集:佣金/返利/赞助/优先位');
221
+ assert(COMMISSION_CERTAINTIES.join(',') === 'known_benefit,known_none,unknown', '确定性三态:明确有/明确无/未知(unknown 不默认 none)');
222
+ assert(COMMISSION_SOURCES.join(',') === 'supplier_field,contract_rule' && COMMISSION_AMOUNT_BASES.join(',') === 'fixed,percent_of_total,per_night', '来源与金额口径基准封闭集');
223
+ const undisclosed = disclosureForConfirmation(undefined);
224
+ assert(!undisclosed.ok && undisclosed.violation === 'commission_undisclosed', '确认边界无披露对象 ⇒ commission_undisclosed 拒绝');
225
+ assert(validateCommissionDisclosure(disclosureOk).ok, '合法佣金披露(供应商字段来源,10% 佣金)通过');
226
+ const noneWithoutSource = validateCommissionDisclosure({
227
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
228
+ certainty: 'known_none',
229
+ benefits: [],
230
+ source: 'supplier_field',
231
+ });
232
+ assert(!noneWithoutSource.ok && noneWithoutSource.violations.includes('commission_none_without_source'), '「明确无受益」无 contract_rule 来源被拒(无受益也必须是记录结论,不是沉默)');
233
+ const emptyBenefit = validateCommissionDisclosure({
234
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
235
+ certainty: 'known_benefit',
236
+ benefits: [],
237
+ source: 'supplier_field',
238
+ });
239
+ assert(!emptyBenefit.ok && emptyBenefit.violations.includes('commission_benefit_incomplete'), 'known_benefit 但受益列表为空 ⇒ commission_benefit_incomplete');
240
+ const unknownKept = validateCommissionDisclosure({
241
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
242
+ certainty: 'unknown',
243
+ benefits: [],
244
+ source: 'supplier_field',
245
+ });
246
+ assert(unknownKept.ok, 'unknown 是合法且独立的披露形态(不强制折成 none)');
247
+ const basisConflict = validateCommissionDisclosure({
248
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
249
+ certainty: 'known_benefit',
250
+ benefits: [{ kind: 'commission', paidBy: 'supplier', paidTo: 'gotry', basis: { type: 'percent_of_total', percent: '10', amount: '120', currency: 'CNY' } }],
251
+ source: 'supplier_field',
252
+ });
253
+ assert(!basisConflict.ok && basisConflict.violations.includes('commission_amount_basis_inconsistent'), '口径不一致:percent_of_total 同时携带固定金额 ⇒ commission_amount_basis_inconsistent');
254
+ const fixedNoCurrency = validateCommissionDisclosure({
255
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
256
+ certainty: 'known_benefit',
257
+ benefits: [{ kind: 'rebate', paidBy: 'distributor', paidTo: 'gotry', basis: { type: 'fixed', amount: '50' } }],
258
+ source: 'contract_rule',
259
+ });
260
+ assert(!fixedNoCurrency.ok && fixedNoCurrency.violations.includes('commission_amount_basis_inconsistent'), 'fixed 口径缺币种 ⇒ 口径不一致拒绝');
261
+ const d1 = computeDisclosureDigest(disclosureOk);
262
+ const d1Reordered = computeDisclosureDigest({
263
+ source: 'supplier_field',
264
+ certainty: 'known_benefit',
265
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
266
+ benefits: [{ basis: { percent: '10', type: 'percent_of_total' }, paidTo: 'gotry', paidBy: 'supplier', kind: 'commission' }],
267
+ });
268
+ assert(d1 === d1Reordered && /^[0-9a-f]{64}$/.test(d1), '披露 digest=canonical JSON 的 SHA-256(键序无关,稳定)');
269
+ const d2 = computeDisclosureDigest({
270
+ schema: COMMISSION_DISCLOSURE_SCHEMA,
271
+ certainty: 'known_benefit',
272
+ benefits: [{ kind: 'commission', paidBy: 'supplier', paidTo: 'gotry', basis: { type: 'percent_of_total', percent: '12.5' } }],
273
+ source: 'supplier_field',
274
+ });
275
+ assert(d1 !== d2, '佣金口径变化(10%→12.5%)⇒ digest 不同');
276
+ const fpFirst = bindDisclosureToFingerprint({}, disclosureOk);
277
+ assert(fpFirst.ok && fpFirst.fingerprint[COMMISSION_DISCLOSURE_FINGERPRINT_FIELD] === d1, '披露 digest 写入指纹字段 commission_disclosure');
278
+ const fpSame = bindDisclosureToFingerprint({ [COMMISSION_DISCLOSURE_FINGERPRINT_FIELD]: d1 }, disclosureOk);
279
+ assert(fpSame.ok, '同口径披露与既有指纹一致 ⇒ 绑定通过');
280
+ const fpChanged = bindDisclosureToFingerprint({ [COMMISSION_DISCLOSURE_FINGERPRINT_FIELD]: d1 }, { ...disclosureOk, benefits: [{ kind: 'commission', paidBy: 'supplier', paidTo: 'gotry', basis: { type: 'percent_of_total', percent: '12.5' } }] });
281
+ assert(!fpChanged.ok && fpChanged.violation === 'disclosure_digest_mismatch', '披露口径变化 ⇒ 指纹 digest mismatch ⇒ 旧 receipt 失效需重确认');
282
+ }
283
+ // ---- 6:账本分词与文案分词一致 ----------------------------------------------------
284
+ {
285
+ const cancelByStatus = new Map(CANCELLATION_STATUSES.map(status => {
286
+ const r = cancellationOutcome({
287
+ schema: CANCELLATION_OUTCOME_SCHEMA,
288
+ status,
289
+ customerReferenceNo: 'GT-COPY',
290
+ observedAt: '2026-09-11T10:00:00.000Z',
291
+ });
292
+ return [status, isOk(r) ? r.value : undefined];
293
+ }));
294
+ // cancel_submitted 需要供应商回执才允许「已取消」:单列一个带凭证的变体
295
+ const srn = supplierReferenceNoFromReturn('SR-COPY', 'supplier_cancel_return');
296
+ const cancelSubmittedConfirmed = cancellationOutcome({
297
+ schema: CANCELLATION_OUTCOME_SCHEMA,
298
+ status: 'cancel_submitted',
299
+ customerReferenceNo: 'GT-COPY',
300
+ supplierReferenceNo: isOk(srn) ? srn.value : undefined,
301
+ supplierCancellationConfirmed: { digest: 'sha256:copy-digest' },
302
+ observedAt: '2026-09-11T10:00:00.000Z',
303
+ });
304
+ if (isOk(cancelSubmittedConfirmed))
305
+ cancelByStatus.set('cancel_submitted', cancelSubmittedConfirmed.value);
306
+ const refundByStatus = new Map(REFUND_STATUSES.map(status => {
307
+ const input = {
308
+ schema: REFUND_OUTCOME_SCHEMA,
309
+ status,
310
+ observedAt: '2026-09-11T10:00:00.000Z',
311
+ };
312
+ if (status === 'refunded') {
313
+ input.customerRefund = { amount: '100', currency: 'CNY', source: 'supplier_refund_record' };
314
+ input.evidence = { kind: 'supplier_refund_record', digest: 'sha256:copy-refund' };
315
+ }
316
+ const r = refundOutcome(input);
317
+ return [status, isOk(r) ? r.value : undefined];
318
+ }));
319
+ assert([...cancelByStatus.values()].every(v => v !== undefined) && [...refundByStatus.values()].every(v => v !== undefined), '文案矩阵:取消 4 态×退款 5 态全部可构造');
320
+ const disclosures = [undefined, { schema: COMMISSION_DISCLOSURE_SCHEMA, certainty: 'unknown', benefits: [], source: 'supplier_field' }, { schema: COMMISSION_DISCLOSURE_SCHEMA, certainty: 'known_none', benefits: [], source: 'contract_rule' }, disclosureOk];
321
+ let combos = 0;
322
+ let violationsFound = 0;
323
+ for (const cancel of cancelByStatus.values()) {
324
+ for (const refund of refundByStatus.values()) {
325
+ for (const ledger of LOCAL_LEDGER_STATUSES) {
326
+ for (const locale of ['en', 'zh-CN']) {
327
+ for (const disclosure of disclosures) {
328
+ const c = cancel;
329
+ const r = refund;
330
+ const text = renderAftercareCopy(c, r, ledger, disclosure, locale);
331
+ const violations = copyTokenViolations(text, { cancel: c, refund: r, ledgerStatus: ledger, disclosure, locale });
332
+ combos++;
333
+ violationsFound += violations.length;
334
+ if (violations.length > 0)
335
+ console.error(` combo drift: ${c.status}/${r.status}/${ledger}/${locale}: ${violations.join(',')}`);
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ assert(combos === 4 * 5 * 3 * 2 * 4 && violationsFound === 0, `生成文案在 ${combos} 组状态×账本×语言×披露组合下零违例(账本分词与文案分词一致)`);
342
+ const zh = renderAftercareCopy(cancelByStatus.get('not_requested'), refundByStatus.get('not_applicable'), 'compensated', undefined, 'zh-CN');
343
+ assert(zh.includes('已补偿') && !zh.includes('已退款') && !zh.includes('退款已到账'), 'pending→compensated:文案只说本地账面补偿,绝无「已退款」措辞(A-5 反例)');
344
+ const en = renderAftercareCopy(cancelByStatus.get('not_requested'), refundByStatus.get('not_applicable'), 'compensated', undefined, 'en');
345
+ assert(/compensated/i.test(en) && !/refunded/i.test(en), 'EN 同律:compensated 不产生 refunded 措辞');
346
+ assert(localLedgerCopy('compensated', 'zh-CN').includes('补偿') && !localLedgerCopy('compensated', 'zh-CN').includes('已退款'), '账本 compensated 单独词条也不含退款到账措辞');
347
+ const submittedZh = renderAftercareCopy((() => {
348
+ const r = cancellationOutcome({ schema: CANCELLATION_OUTCOME_SCHEMA, status: 'cancel_submitted', customerReferenceNo: 'GT-W', observedAt: '2026-09-11T10:00:00.000Z' });
349
+ return isOk(r) ? r.value : undefined;
350
+ })(), refundByStatus.get('not_applicable'), 'pending', undefined, 'zh-CN');
351
+ assert(submittedZh.includes('已提交取消申请') && !submittedZh.includes('已取消'), '仅提交取消:文案为「已提交取消申请」,无「已取消」');
352
+ const pendingEn = renderAftercareCopy(cancelByStatus.get('not_requested'), refundByStatus.get('refund_pending'), 'confirmed', undefined, 'en');
353
+ assert(/refund pending/i.test(pendingEn) && !/refunded/i.test(pendingEn), '退款超时窗口:文案保持 refund pending,不写 refunded');
354
+ // 手写/LLM 漂移文案反例(生成器之外的说法必须过同一分词闸)
355
+ const cancelW = cancellationOutcome({ schema: CANCELLATION_OUTCOME_SCHEMA, status: 'cancel_submitted', customerReferenceNo: 'GT-W', observedAt: '2026-09-11T10:00:00.000Z' });
356
+ const refundPendingW = refundByStatus.get('refund_pending');
357
+ const refundNAW = refundByStatus.get('not_applicable');
358
+ if (isOk(cancelW)) {
359
+ const state = { cancel: cancelW.value, refund: refundPendingW, ledgerStatus: 'confirmed', disclosure: undefined, locale: 'en' };
360
+ assert(copyTokenViolations('Your booking is cancelled and you have been refunded.', state).includes('copy_claimed_cancelled_without_supplier_evidence'), '反例:无供应商回执却写 cancelled 被抓');
361
+ assert(copyTokenViolations('Your booking is cancelled and you have been refunded.', state).includes('copy_claimed_refund_arrival_without_authority'), '反例:pending 态写 refunded 被抓');
362
+ assert(copyTokenViolations('Cancellation requested; refund pending.', state).length === 0, '合规措辞(cancellation requested / refund pending)零违例');
363
+ const compState = { cancel: cancelW.value, refund: refundNAW, ledgerStatus: 'compensated', disclosure: undefined, locale: 'zh-CN' };
364
+ assert(copyTokenViolations('已为您取消并退款已到账。', compState).includes('copy_refund_wording_on_local_compensation_only'), '反例:本地 compensated + 无退款结果却写「退款已到账」被抓(账本分词≠退款分词)');
365
+ assert(copyTokenViolations('本地账面已补偿;退款状态以退款结果为准。', compState).length === 0, '合规措辞:compensated 只述账务动作零违例');
366
+ }
367
+ const stateWithBenefit = {
368
+ cancel: cancelByStatus.get('not_requested'),
369
+ refund: refundNAW,
370
+ ledgerStatus: 'confirmed',
371
+ disclosure: disclosureOk,
372
+ locale: 'en',
373
+ };
374
+ assert(copyTokenViolations('Total: CNY 1200. Booking confirmed.', stateWithBenefit).includes('commission_disclosure_missing_from_summary'), '反例:摘要只报总价、佣金藏进总价不披露被抓');
375
+ assert(copyTokenViolations('Total: CNY 1200. Commission: paid by supplier to gotry, 10% of total.', stateWithBenefit).length === 0, '合规:佣金显式披露(谁付/给谁/口径)零违例');
376
+ const stateUnknown = { ...stateWithBenefit, disclosure: { schema: COMMISSION_DISCLOSURE_SCHEMA, certainty: 'unknown', benefits: [], source: 'supplier_field' } };
377
+ assert(copyTokenViolations('Commission benefit: unknown (not assumed none).', stateUnknown).length === 0, 'unknown 披露的合规措辞零违例(不默认 none)');
378
+ assert(copyTokenViolations('Commission: 10% of total paid by supplier.', stateUnknown).includes('commission_status_contradicts_summary'), '反例:披露为 unknown 却给出确定受益口径被抓');
379
+ }
380
+ // ---- 7:纯契约面(零网络/零子进程/零账本写入,非运行时激活) ------------------------
381
+ {
382
+ const src = readFileSync(new URL('../src/booking-surface/cancel-refund-commission.js', import.meta.url), 'utf8');
383
+ const forbiddenImports = ['node:net', 'node:http', 'node:https', 'node:tls', 'node:dgram', 'node:child_process', 'node:fs', 'node:sqlite'];
384
+ assert(forbiddenImports.every(name => !src.includes(`'${name}'`) && !src.includes(`"${name}"`)), '模块不 import 任何网络/子进程/文件系统/SQLite 内建(纯契约)');
385
+ assert(!/\bfetch\(|\bspawn\(|\bexecFile\(|\bexecSync\(|createConnection|connect\(/.test(src), '模块无 fetch/spawn/exec/连接调用面(无真实交易路径)');
386
+ const nodeImports = [...src.matchAll(/from '([^']+)'/g)].map(m => m[1]).filter(s => s.startsWith('node:'));
387
+ assert(nodeImports.every(s => s === 'node:crypto'), `node 内建依赖仅 node:crypto(实际:${JSON.stringify(nodeImports)})`);
388
+ assert(!src.includes('dsh-runtime') && !src.includes('gotry-state'), '模块不触碰运行时状态目录(不写共享状态)');
389
+ }
390
+ console.log(`\nISSUE 233 CANCEL/REFUND/COMMISSION TESTS: ${pass} ok, ${fail} fail`);
391
+ if (fail > 0)
392
+ process.exit(1);