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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,3 +1,24 @@
1
+ /**
2
+ * staicli(hbcli)全流程端到端测试:二进制 → 隔离凭证面 → 真实 UAT 取票 →
3
+ * 能力层真实 spawn → 效应解译层策略(ADR-18)→ 工具层 gotry_hotel_search。
4
+ *
5
+ * 账号与凭证纪律(红线):
6
+ * - 测试账号用 hotel-be 种子沙箱 API 账号 hotelbyte_api_demo/hotelbyte_api_demo
7
+ * (user/domain/predefined_user_demo.go,IsSandbox:true,专为 API 接入验证设计);
8
+ * 正式集成方换自己申请的 hbk_ / hbs_ 前缀凭证对。
9
+ * - 凭证写进 mkdtemp 的隔离 STAICLI_HOME,全程不读不写用户默认 ~/.staicli
10
+ * (巡检状态纪律:测试不得触碰真实用户凭证与产品状态);
11
+ * - stateRoot 同样走临时目录,结束即删。
12
+ *
13
+ * SKIP 语义(同 §17 先例:能跑则跑,环境缺前置不红):
14
+ * - hbcli 未安装 → SKIP + 安装指引(npx @danceiny/gotry setup);
15
+ * - UAT(api-test.hotelbyte.com)不可达 → SKIP + 原因。
16
+ * UAT 当前库存态(2026-08-30 实测):目的地/酒店参考数据为空,hotel-list 按名
17
+ * 查询返回业务层 404——通道/鉴权/搜索编排(供应商 provenance+correlationId)均
18
+ * 真实工作,故断言「通道真实性 + 降级诚实性」而非「有库存返回」。
19
+ *
20
+ * 运行(显式 UAT opt-in): cd ts && GOTRY_HBCLI_LIVE=1 npx tsx scripts/hbcli-e2e-tests.ts
21
+ */
1
22
  import { spawnSync } from 'node:child_process';
2
23
  import { mkdtempSync, rmSync } from 'node:fs';
3
24
  import { tmpdir } from 'node:os';
@@ -6,121 +27,101 @@ import assert from 'node:assert/strict';
6
27
  import { listDestinations, searchHotels, hbcliBinCandidates } from '../capabilities/hbcli.js';
7
28
  import { makeProductionInterpreter } from '../capabilities/effect.js';
8
29
  const UAT = 'https://api-test.hotelbyte.com';
30
+ /** hotel-be 种子沙箱 API 账号(公开演示用,IsSandbox;真实集成方应换自己申请的 hbk_/hbs_ 前缀凭证对) */
9
31
  const SANDBOX_APP_KEY = 'hotelbyte_api_demo';
10
32
  const SANDBOX_APP_SECRET = 'hotelbyte_api_demo';
11
33
  const GUIDANCE = [
12
34
  'staicli(hbcli)账号配置指引:',
13
- ' 1. 安装 CLI(若缺):npx gotry setup(npm 装 staicli@npmjs → PATH 的 hbcli)',
35
+ ' 1. 安装 CLI(若缺):npx @danceiny/gotry setup(npm 装 staicli@npmjs → PATH 的 hbcli)',
14
36
  ' 2. 快速试用(沙箱演示账号,来自 hotel-be 种子,user/domain/predefined_user_demo.go):',
15
37
  ' hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo',
16
38
  ' 3. 正式接入:向 HotelByte 申请专属 appKey/appSecret(hbk_*/hbs_*),替换第 2 步凭证;',
17
39
  ' 门户模式则 hbcli auth login --username <email>(权限更大,搜索场景无必要)',
18
- ' 4. 自检:hbcli auth whoami(api_key.configured=true 即凭证就位;has_ticket 首次查询后为 true)'
40
+ ' 4. 自检:hbcli auth whoami(api_key.configured=true 即凭证就位;has_ticket 首次查询后为 true)',
19
41
  ];
20
42
  function probeBin() {
21
- for (const bin of hbcliBinCandidates('hbcli')){
22
- const r = spawnSync(bin, [
23
- 'version'
24
- ], {
25
- timeout: 10_000,
26
- stdio: 'ignore'
27
- });
28
- if (!r.error && r.status === 0) return bin;
43
+ for (const bin of hbcliBinCandidates('hbcli')) {
44
+ const r = spawnSync(bin, ['version'], { timeout: 10_000, stdio: 'ignore' });
45
+ if (!r.error && r.status === 0)
46
+ return bin;
29
47
  }
30
48
  return null;
31
49
  }
32
50
  async function uatReachable() {
33
51
  try {
34
52
  await fetch(`${UAT}/api/search/destinations`, {
35
- method: 'POST',
36
- headers: {
37
- 'Content-Type': 'application/json'
38
- },
39
- body: '{}',
40
- signal: AbortSignal.timeout(8_000)
53
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
54
+ signal: AbortSignal.timeout(8_000),
41
55
  });
42
- return true;
43
- } catch {
56
+ return true; // 任何 HTTP 响应(含 401)都算可达
57
+ }
58
+ catch {
44
59
  return false;
45
60
  }
46
61
  }
47
62
  async function main() {
63
+ // This guard must stay before guidance-dependent probes, binary discovery,
64
+ // network reachability, STAICLI_HOME setup, and every credential operation.
65
+ // GOTRY_SESSION_LIVE intentionally does not control this supplier path.
66
+ if (process.env.GOTRY_HBCLI_LIVE !== '1') {
67
+ console.log('SKIP: HotelByte UAT 已关闭(默认离线);如需真实 UAT 请显式设置 GOTRY_HBCLI_LIVE=1——本路径未探测 hbcli/网络/凭证');
68
+ return;
69
+ }
48
70
  console.log(GUIDANCE.join('\n'));
49
71
  const bin = probeBin();
50
72
  if (!bin) {
51
- console.log(`SKIP: 未安装 hbcli(候选 ${hbcliBinCandidates('hbcli').join(' / ')} 均不可执行)——npx gotry setup 可按官方脚本安装`);
73
+ console.log(`SKIP: 未安装 hbcli(候选 ${hbcliBinCandidates('hbcli').join(' / ')} 均不可执行)——npx @danceiny/gotry setup 可按官方脚本安装`);
52
74
  return;
53
75
  }
54
- if (!await uatReachable()) {
76
+ if (!(await uatReachable())) {
55
77
  console.log(`SKIP: UAT(${UAT})不可达——全流程端到端在有网环境跑,离线降级面已由 hbcli-tests.ts(§7)覆盖`);
56
78
  return;
57
79
  }
80
+ // 隔离凭证面:全程不触碰用户默认 ~/.staicli
58
81
  const staicliHome = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-'));
59
82
  const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-state-'));
60
83
  process.env.STAICLI_HOME = staicliHome;
61
84
  const fallbackPath = join(import.meta.dirname, '..', '..', 'data', 'hotels_2026.json');
62
85
  try {
63
- const set = spawnSync(bin, [
64
- 'auth',
65
- 'set-credentials',
66
- '--app-key',
67
- SANDBOX_APP_KEY,
68
- '--app-secret',
69
- SANDBOX_APP_SECRET
70
- ], {
71
- timeout: 30_000,
72
- encoding: 'utf-8'
73
- });
86
+ // 1) 沙箱账号写入隔离凭证面(hbcli auth set-credentials)
87
+ const set = spawnSync(bin, ['auth', 'set-credentials', '--app-key', SANDBOX_APP_KEY, '--app-secret', SANDBOX_APP_SECRET], { timeout: 30_000, encoding: 'utf-8' });
74
88
  assert.equal(set.status, 0, `auth set-credentials 应成功,实际 ${set.status}: ${set.stderr?.slice(0, 200)}`);
75
89
  console.log(`1. 沙箱账号写入隔离 STAICLI_HOME(${staicliHome.replace(tmpdir(), '$TMPDIR')})OK`);
76
- const who = spawnSync(bin, [
77
- 'auth',
78
- 'whoami'
79
- ], {
80
- timeout: 30_000,
81
- encoding: 'utf-8'
82
- });
90
+ // 1b) whoami 自检:api_key.configured=true(配置面就位,尚未取票)
91
+ const who = spawnSync(bin, ['auth', 'whoami'], { timeout: 30_000, encoding: 'utf-8' });
83
92
  assert.equal(who.status, 0, 'whoami 应 exit 0');
84
93
  assert.match(who.stdout, /"configured": true/, 'whoami 应报 api_key.configured=true');
85
94
  console.log('1b. hbcli auth whoami 自检(api_key.configured=true)OK');
86
- const dest = await listDestinations({
87
- timeoutMs: 30_000
88
- });
95
+ // 2) 能力层真实 spawn:目的地列表(通道真实性——UAT 参考数据可为空,通道必须真实)
96
+ const dest = await listDestinations({ timeoutMs: 30_000 });
89
97
  assert.equal(dest.via, 'hbcli-realtime', `destinations 应走实时通道,实际 ${dest.via}(${dest.error ?? ''})`);
90
98
  assert.equal(dest.exitCode, 0, 'destinations 应 exit 0');
91
99
  assert.match(dest.evidence, /\[实时API:hbcli@\d{4}-/, '证据链应带实时时间戳标注(L4 不变量)');
92
100
  assert.ok(dest.result !== null && typeof dest.result === 'object', '实时返回应可解析为 JSON');
93
101
  console.log(`2. 能力层实时通道(listDestinations → ${UAT})via=hbcli-realtime,证据链 OK`);
94
- const hotels = await searchHotels({
95
- destination: '深圳',
96
- checkIn: '2026-09-18',
97
- checkOut: '2026-09-20',
98
- adults: 2
99
- }, {
100
- timeoutMs: 30_000,
101
- fallbackPath
102
- });
102
+ // 3) hotel-list 全链路:UAT 无目的地参考数据 → 业务层 404 → 降级诚实性
103
+ // (真实失败不再是「工具坏了」,而是带上游原话的显式降级;issue #24 契约)
104
+ const hotels = await searchHotels({ destination: '深圳', checkIn: '2026-09-18', checkOut: '2026-09-20', adults: 2 }, { timeoutMs: 30_000, fallbackPath });
103
105
  if (hotels.via === 'hbcli-error') {
104
106
  assert.ok(hotels.error, '降级结果应携带上游原话原因');
105
107
  assert.match(hotels.summary, /降级/, 'summary 应显式声明降级(不伪装成实时)');
106
108
  assert.match(hotels.summary, /静态包/, '降级路径应指明静态包来源');
109
+ // 静态包含深圳块(data/hotels_2026.json)→ 降级应命中并标注「非实时」
107
110
  assert.ok(hotels.hotels !== null, '静态包深圳块应命中');
108
111
  assert.match(hotels.summary, /非实时/, '降级产物必须显式标注非实时(L4 反伪装红线)');
109
112
  console.log(`3. hotel-list 真实降级链(via=hbcli-error,原因:${String(hotels.error).slice(0, 60)}…;静态包命中+非实时标注)OK`);
110
- } else {
113
+ }
114
+ else {
115
+ // UAT 后续补齐目的地数据时走此分支:实时返回必须带证据链
111
116
  assert.equal(hotels.via, 'hbcli-realtime');
112
117
  assert.match(hotels.evidence, /\[实时API:hbcli@\d{4}-/);
113
118
  console.log('3. hotel-list 实时返回(UAT 已有目的地数据)证据链 OK');
114
119
  }
115
- const itp = await makeProductionInterpreter({
116
- breakers: new Map()
117
- })({
120
+ // 4) 效应解译层(ADR-18)真实通道:HBCLI_HOTEL_SEARCH 策略=永不重试+熔断在册;
121
+ // 独立 breakers Map,不污染产品单例
122
+ const itp = await makeProductionInterpreter({ breakers: new Map() })({
118
123
  effect: 'HBCLI_HOTEL_SEARCH',
119
- params: {
120
- destination: '深圳',
121
- fallbackPath,
122
- timeoutMs: 30_000
123
- }
124
+ params: { destination: '深圳', fallbackPath, timeoutMs: 30_000 },
124
125
  });
125
126
  assert.equal(itp.trace.attempts, 1, `hbcli 策略永不重试,实际 attempts=${itp.trace.attempts}`);
126
127
  assert.equal(itp.trace.channel, 'cli');
@@ -129,50 +130,30 @@ async function main() {
129
130
  const viaAfter = itp.result.via;
130
131
  assert.ok(viaAfter === 'hbcli-error' || viaAfter === 'hbcli-realtime', `渠道 via 应保真,实际 ${viaAfter}`);
131
132
  console.log(`4. 效应解译层(attempts=1 永不重试,breaker=${itp.trace.breaker},via=${viaAfter})OK`);
133
+ // 5) 工具层端到端:gotry_hotel_search 经效应解译器跑完整链(隔离 stateRoot)
132
134
  const { apply } = await import('../src/index.js');
133
135
  const registered = [];
134
- apply({
135
- tools: {
136
- register: (t)=>registered.push(t)
137
- }
138
- }, {
139
- stateRoot,
140
- timeoutMs: 30_000,
141
- hbcliBin: 'hbcli',
142
- sessionAccess: 'ask'
143
- });
144
- const tool = registered.find((t)=>t.name === 'gotry_hotel_search');
136
+ apply({ tools: { register: (t) => registered.push(t) } }, { stateRoot, timeoutMs: 30_000, hbcliBin: 'hbcli', sessionAccess: 'ask' });
137
+ const tool = registered.find(t => t.name === 'gotry_hotel_search');
145
138
  assert.ok(tool, 'gotry_hotel_search 应已注册');
146
- const obs = await tool.execute({
147
- destination: '深圳'
148
- }, null);
139
+ const obs = await tool.execute({ destination: '深圳', checkIn: '2026-09-18', checkOut: '2026-09-20' }, null);
149
140
  assert.equal(obs.ok, true, '工具面应返回 ok 包络(永不抛错契约)');
150
141
  assert.ok(obs.via === 'hbcli-error' || obs.via === 'hbcli-realtime', `工具面 via 应与渠道一致,实际 ${obs.via}`);
151
142
  const ev = String(obs.evidence);
152
143
  if (obs.via === 'hbcli-error') {
153
144
  assert.match(ev, /静态包/, '降级时证据链应标注静态包(估算必须显式)');
154
- } else {
145
+ }
146
+ else {
155
147
  assert.match(ev, /实时API:hbcli/, '实时时证据链应标注 hbcli 时间戳');
156
148
  }
157
149
  assert.ok(String(obs.summary).length > 0, 'summary 应存在');
158
150
  console.log(`5. 工具层端到端(gotry_hotel_search → 效应解译器 → ${UAT})via=${obs.via},evidence=${ev.slice(0, 46)}… OK`);
159
151
  console.log('HBCLI E2E TESTS: 5/5 OK(隔离凭证面/实时通道/降级诚实/解译策略/工具面全链)');
160
- } finally{
152
+ }
153
+ finally {
161
154
  process.env.STAICLI_HOME = undefined;
162
- rmSync(staicliHome, {
163
- recursive: true,
164
- force: true
165
- });
166
- rmSync(stateRoot, {
167
- recursive: true,
168
- force: true
169
- });
155
+ rmSync(staicliHome, { recursive: true, force: true });
156
+ rmSync(stateRoot, { recursive: true, force: true });
170
157
  }
171
158
  }
172
- main().catch((e)=>{
173
- console.error(e);
174
- process.exitCode = 1;
175
- });
176
-
177
-
178
- //# sourceURL=ts/scripts/hbcli-e2e-tests.ts
159
+ main().catch((e) => { console.error(e); process.exitCode = 1; });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Offline supplier opt-in gate proof for issue #318.
3
+ *
4
+ * Spawn the real hbcli-e2e entrypoint with a discoverable fixture binary and
5
+ * a blocked-network/read-write trap. The default entrypoint must return before
6
+ * any probe, so the fixture and traps must all remain untouched.
7
+ *
8
+ * This test never enables GOTRY_HBCLI_LIVE and never calls a supplier endpoint.
9
+ */
10
+ import assert from 'node:assert/strict';
11
+ import { chmod, mkdir, mkdtemp, readFile, writeFile, rm } from 'node:fs/promises';
12
+ import { spawnSync } from 'node:child_process';
13
+ import { tmpdir } from 'node:os';
14
+ import { dirname, join } from 'node:path';
15
+ const root = join(import.meta.dirname, '..', '..');
16
+ const e2e = join(root, 'ts', 'scripts', 'hbcli-e2e-tests.ts');
17
+ const fixtureRoot = await mkdtemp(join(tmpdir(), 'gotry-issue-318-'));
18
+ const fixtureHome = join(fixtureRoot, 'home');
19
+ const fixtureBin = join(fixtureHome, '.local', 'bin', 'hbcli');
20
+ const counters = join(fixtureRoot, 'counters');
21
+ const launchCounter = join(counters, 'binary-launches');
22
+ const networkCounter = join(counters, 'network-attempts');
23
+ const credentialCounter = join(counters, 'credential-reads-writes');
24
+ const trap = join(fixtureRoot, 'blocked-network-and-credential-trap.mjs');
25
+ await mkdir(dirname(fixtureBin), { recursive: true });
26
+ await mkdir(counters, { recursive: true });
27
+ await writeFile(fixtureBin, `#!/bin/sh
28
+ printf '1\\n' >> "$HBCLI_LAUNCH_COUNTER"
29
+ printf 'fixture hbcli must not be started in offline mode\\n' >&2
30
+ exit 97
31
+ `);
32
+ await chmod(fixtureBin, 0o755);
33
+ await writeFile(trap, `import fs from 'node:fs'
34
+ import { appendFileSync } from 'node:fs'
35
+ import { syncBuiltinESMExports } from 'node:module'
36
+
37
+ const networkCounter = ${JSON.stringify(networkCounter)}
38
+ const credentialCounter = ${JSON.stringify(credentialCounter)}
39
+ const credentialRoot = ${JSON.stringify(fixtureHome)}
40
+ const realFetch = globalThis.fetch
41
+ globalThis.fetch = async (...args) => {
42
+ appendFileSync(networkCounter, JSON.stringify(args[0]) + '\\n')
43
+ throw new Error('blocked network in issue #318 proof')
44
+ }
45
+ const isCredentialPath = (value) => String(value).startsWith(credentialRoot + '/.staicli')
46
+ const realReadFileSync = fs.readFileSync
47
+ fs.readFileSync = function (path, ...args) {
48
+ if (isCredentialPath(path)) appendFileSync(credentialCounter, 'read\\n')
49
+ return realReadFileSync.call(this, path, ...args)
50
+ }
51
+ const realWriteFileSync = fs.writeFileSync
52
+ fs.writeFileSync = function (path, ...args) {
53
+ if (isCredentialPath(path)) appendFileSync(credentialCounter, 'write\\n')
54
+ return realWriteFileSync.call(this, path, ...args)
55
+ }
56
+ syncBuiltinESMExports()
57
+ void realFetch
58
+ `);
59
+ try {
60
+ for (const optIn of [undefined, '0', 'true', 'yes']) {
61
+ const env = {
62
+ ...process.env,
63
+ HOME: fixtureHome,
64
+ PATH: `${dirname(fixtureBin)}:${process.env.PATH ?? ''}`,
65
+ NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --import=${trap}`.trim(),
66
+ HBCLI_LAUNCH_COUNTER: launchCounter,
67
+ STAICLI_HOME: join(fixtureHome, '.staicli'),
68
+ };
69
+ if (optIn === undefined)
70
+ delete env.GOTRY_HBCLI_LIVE;
71
+ else
72
+ env.GOTRY_HBCLI_LIVE = optIn;
73
+ const result = spawnSync('npx', ['--no-install', 'tsx', e2e], {
74
+ cwd: join(root, 'ts'), encoding: 'utf8', env,
75
+ });
76
+ assert.equal(result.status, 0, `offline entrypoint should exit 0 for ${String(optIn)}, stderr=${result.stderr}`);
77
+ assert.match(result.stdout, /SKIP: HotelByte UAT 已关闭/);
78
+ assert.match(result.stdout, /未探测 hbcli\/网络\/凭证/);
79
+ }
80
+ for (const [path, label] of [
81
+ [launchCounter, 'fixture binary launches'],
82
+ [networkCounter, 'network attempts'],
83
+ [credentialCounter, 'credential reads/writes'],
84
+ ]) {
85
+ let observed = '';
86
+ try {
87
+ observed = await readFile(path, 'utf8');
88
+ }
89
+ catch { /* absent means zero */ }
90
+ assert.equal(observed, '', `${label} must remain zero, observed=${observed}`);
91
+ }
92
+ console.log('HBCLI LIVE OPT-IN PROOF: real entrypoint + discoverable fixture + blocked network; unset/0/invalid => binary=0/network=0/credential=0');
93
+ }
94
+ finally {
95
+ await rm(fixtureRoot, { recursive: true, force: true });
96
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * #232 staicli release contract (test-only):
3
+ * - verify a caller-supplied local npm tarball against the recorded bytes;
4
+ * - inspect and execute the actual packaged hbcli entrypoint's safe help surface;
5
+ * - prove the unsupported tenant selector is rejected by the actual parser;
6
+ * - reject a one-byte tampered artifact before extraction/execution.
7
+ *
8
+ * This script never downloads, runs npm lifecycle scripts, reads credentials, or
9
+ * calls HotelByte/supplier endpoints. It does not classify supplier outcomes;
10
+ * those require recorded supplier response fixtures and the runtime adapter
11
+ * remains outside this preparation slice.
12
+ *
13
+ * Usage:
14
+ * cd ts && npx tsx scripts/hbcli-release-contract-tests.ts /path/to/staicli-0.0.3.tgz
15
+ * STAICLI_TARBALL=/path/to/staicli-0.0.3.tgz npx tsx scripts/hbcli-release-contract-tests.ts
16
+ */
17
+ import assert from 'node:assert/strict';
18
+ import { createHash } from 'node:crypto';
19
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
20
+ import { spawnSync } from 'node:child_process';
21
+ import { tmpdir } from 'node:os';
22
+ import { dirname, join, resolve } from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+ const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'fixtures', 'staicli-0.0.3.release-contract.json');
25
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
26
+ const tarballPath = process.argv[2] ?? process.env.STAICLI_TARBALL;
27
+ if (!tarballPath) {
28
+ console.log('SKIP: no local staicli tarball supplied; pass argv[2] or STAICLI_TARBALL for artifact-byte verification');
29
+ process.exit(0);
30
+ }
31
+ assert.equal(manifest.schema_version, 'gotry_staicli_release_contract.v1');
32
+ assert.ok(existsSync(tarballPath), `tarball must exist: ${tarballPath}`);
33
+ function digest(algorithm, bytes) {
34
+ return createHash(algorithm).update(bytes).digest(algorithm === 'sha512' ? 'base64' : 'hex');
35
+ }
36
+ function sha256Text(text) {
37
+ return digest('sha256', Buffer.from(text));
38
+ }
39
+ function verifyTarball(path) {
40
+ const bytes = readFileSync(path);
41
+ const actualIntegrity = `sha512-${digest('sha512', bytes)}`;
42
+ assert.equal(actualIntegrity, manifest.artifact.integrity, 'tarball bytes must match the recorded npm integrity');
43
+ assert.equal(digest('sha256', bytes), manifest.artifact.sha256, 'tarball bytes must match the recorded SHA-256');
44
+ return bytes;
45
+ }
46
+ function runNode(entry, argv, home) {
47
+ mkdirSync(home, { recursive: true });
48
+ const result = spawnSync(process.execPath, [entry, ...argv], {
49
+ cwd: home,
50
+ env: {
51
+ HOME: home,
52
+ STAICLI_HOME: join(home, 'staicli'),
53
+ PATH: process.env.PATH ?? '',
54
+ },
55
+ encoding: 'utf8',
56
+ timeout: 10_000,
57
+ killSignal: 'SIGKILL',
58
+ });
59
+ return { status: result.status, signal: result.signal, stdout: result.stdout ?? '', stderr: result.stderr ?? '', error: result.error };
60
+ }
61
+ function assertRecordedCommand(name, entry, command, home) {
62
+ const result = runNode(entry, command.argv, home);
63
+ assert.equal(result.error, undefined, `${name} spawn failed: ${result.error?.message ?? 'unknown error'}`);
64
+ assert.equal(result.signal, null, `${name} exceeded the 10s subprocess bound`);
65
+ assert.equal(result.status, command.exit_code, `${name} exit code must match recorded artifact evidence`);
66
+ assert.equal(sha256Text(result.stdout), command.stdout_sha256, `${name} stdout must match recorded artifact evidence`);
67
+ assert.equal(sha256Text(result.stderr), command.stderr_sha256, `${name} stderr must match recorded artifact evidence`);
68
+ for (const line of command.required_lines)
69
+ assert.ok(result.stdout.includes(line), `${name} help must contain: ${line}`);
70
+ }
71
+ const workspace = mkdtempSync(join(tmpdir(), 'gotry-staicli-contract-'));
72
+ const unpacked = join(workspace, 'unpacked');
73
+ const tamperedPath = join(workspace, 'tampered.tgz');
74
+ const isolatedHome = join(workspace, 'home');
75
+ const extractedPackage = join(unpacked, 'package');
76
+ mkdirSync(unpacked);
77
+ try {
78
+ // 1. The recorded integrity is checked against the actual supplied tarball bytes.
79
+ const originalBytes = verifyTarball(tarballPath);
80
+ console.log(`1. tarball bytes verified (${manifest.artifact.package.name}@${manifest.artifact.package.version}, ${manifest.artifact.integrity})`);
81
+ // 2. A one-byte corruption must fail the same verification before execution.
82
+ const corrupted = Buffer.from(originalBytes);
83
+ corrupted[corrupted.length - 1] ^= 1;
84
+ writeFileSync(tamperedPath, corrupted);
85
+ assert.throws(() => verifyTarball(tamperedPath), /tarball bytes must match the recorded npm integrity/);
86
+ console.log('2. one-byte tarball corruption rejected by integrity verification');
87
+ // 3. Extract with tar only; npm is not invoked, and package scripts cannot run.
88
+ const extraction = spawnSync('tar', ['-xzf', tarballPath, '-C', unpacked], {
89
+ encoding: 'utf8',
90
+ timeout: 10_000,
91
+ killSignal: 'SIGKILL',
92
+ });
93
+ assert.equal(extraction.error, undefined, `tar spawn failed: ${extraction.error?.message ?? 'unknown error'}`);
94
+ assert.equal(extraction.signal, null, 'tar extraction exceeded the 10s subprocess bound');
95
+ assert.equal(extraction.status, 0, `tar extraction failed: ${extraction.stderr ?? ''}`);
96
+ const packageJson = JSON.parse(readFileSync(join(extractedPackage, 'package.json'), 'utf8'));
97
+ assert.equal(packageJson.name, manifest.artifact.package.name);
98
+ assert.equal(packageJson.version, manifest.artifact.package.version);
99
+ assert.equal(packageJson.main, manifest.artifact.package.main);
100
+ assert.deepEqual(packageJson.bin, manifest.artifact.package.bin);
101
+ const entry = join(extractedPackage, packageJson.bin.hbcli);
102
+ assert.ok(existsSync(entry), `recorded hbcli bin must exist: ${entry}`);
103
+ console.log(`3. packaged identity/bin verified (${packageJson.name}@${packageJson.version} -> ${packageJson.bin.hbcli})`);
104
+ // 4. Execute only safe help commands with a fresh HOME and STAICLI_HOME.
105
+ for (const [name, command] of Object.entries(manifest.safe_help)) {
106
+ assertRecordedCommand(name, entry, command, join(isolatedHome, name));
107
+ console.log(`4.${name}. actual packaged help surface verified (exit ${command.exit_code})`);
108
+ }
109
+ // 5. The actual Commander parser, not a copied schema, rejects the unsupported selector.
110
+ const selector = runNode(entry, manifest.unsupported_selector.argv, join(isolatedHome, 'selector'));
111
+ assert.equal(selector.error, undefined, `selector parser spawn failed: ${selector.error?.message ?? 'unknown error'}`);
112
+ assert.equal(selector.signal, null, 'selector parser exceeded the 10s subprocess bound');
113
+ assert.equal(selector.status, manifest.unsupported_selector.exit_code);
114
+ assert.equal(sha256Text(selector.stderr), manifest.unsupported_selector.stderr_sha256);
115
+ assert.ok(selector.stderr.includes(manifest.unsupported_selector.stderr_contains));
116
+ assert.equal(selector.stdout, '');
117
+ console.log('5. actual parser rejects --tenant-entity-id without entering a supplier action');
118
+ }
119
+ finally {
120
+ rmSync(workspace, { recursive: true, force: true });
121
+ }
122
+ console.log('HBCLI RELEASE CONTRACT TESTS: 5/5 OK (artifact bytes/help/parser/tamper; no supplier or credential access)');