@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.
- package/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- 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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
161
154
|
process.env.STAICLI_HOME = undefined;
|
|
162
|
-
rmSync(staicliHome, {
|
|
163
|
-
|
|
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)');
|