@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSessionBridge,
|
|
3
|
+
EXTENSION_ORIGIN,
|
|
4
|
+
} from '../../capabilities/session/extension-bridge.js'
|
|
5
|
+
|
|
6
|
+
const created = await createSessionBridge({ ports: [0], extensionOrigin: EXTENSION_ORIGIN })
|
|
7
|
+
if (!created.ok) {
|
|
8
|
+
process.send?.({ kind: 'error', summary: created.summary })
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Parent first parks an external /jobs request, then releases this sole
|
|
13
|
+
// intentional event-loop reference. The default bridge must not keep the
|
|
14
|
+
// child alive after that point.
|
|
15
|
+
const hold = setInterval(() => {}, 60_000)
|
|
16
|
+
process.send?.({ kind: 'listening', port: created.bridge.port })
|
|
17
|
+
process.once('message', (message) => {
|
|
18
|
+
if (typeof message !== 'object' || message === null || !('kind' in message) || message.kind !== 'release') return
|
|
19
|
+
clearInterval(hold)
|
|
20
|
+
process.disconnect?.()
|
|
21
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #363 — 航班/酒店锚点字段指纹(注册 `gotry_fact_gate` 路径 E2E)。
|
|
3
|
+
*
|
|
4
|
+
* 真实执行面:在临时 stateRoot 下装载 gotry-tools 插件,通过 `apply(ctx, config)`
|
|
5
|
+
* 装载注册定义,捕获 `gotry_fact_gate` 的 execute 函数,以 markdown 直接调用——
|
|
6
|
+
* 经注册的 tool definition 走完 `loadFactRegistry(stateRoot)` → `gateArtifact`
|
|
7
|
+
* 公共闸侧路径,完整覆盖 D-26 / D-30 残余收口。
|
|
8
|
+
* 不启动 dsh 宿主、不调用真实 ToolRuntime、不触碰供应商 / browser / policy
|
|
9
|
+
* service / 共享状态。
|
|
10
|
+
*
|
|
11
|
+
* 测试面与 §15 一致:
|
|
12
|
+
* - 3 个根反例 + canonical 各 1 行(共 5)——以注册闸执行;
|
|
13
|
+
* - §4b 价格兼容:总预算/行李费/总计/显式票价/千分位 等形态不被字段指纹吞并;
|
|
14
|
+
* - 价格矛盾仍走 price_contradicted,不退化为 fact_anchor_unknown;
|
|
15
|
+
* - 列车/政策 canonical 不被新字段指纹吞并。
|
|
16
|
+
*
|
|
17
|
+
* 运行(在 ts/ 下):
|
|
18
|
+
* PATH=/opt/homebrew/opt/node@24/bin:$PATH \
|
|
19
|
+
* npx tsx scripts/flight-hotel-anchor-363-registered-e2e.ts
|
|
20
|
+
*/
|
|
21
|
+
import assert from 'node:assert/strict';
|
|
22
|
+
import { mkdtempSync, rmSync, mkdirSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { apply } from '../src/index.js';
|
|
26
|
+
import { BOOKABLE_FACT_SCHEMA, factsFromFlyai, factsFromHotel, makeFactId, renderFlightFact, renderHotelFact, renderPolicyFact, } from '../src/bookable-facts.js';
|
|
27
|
+
import { appendFacts } from '../capabilities/fact-log.js';
|
|
28
|
+
const FETCHED = '2026-08-29T09:51:00.000Z';
|
|
29
|
+
const HOTEL_FETCHED = '2026-09-04T00:00:00.000Z';
|
|
30
|
+
async function main() {
|
|
31
|
+
const home = mkdtempSync(join(tmpdir(), 'gotry-363-flight-hotel-anchor-'));
|
|
32
|
+
const stateRoot = join(home, 'state');
|
|
33
|
+
const cwd = join(home, 'workspace');
|
|
34
|
+
mkdirSync(stateRoot, { recursive: true });
|
|
35
|
+
mkdirSync(cwd, { recursive: true });
|
|
36
|
+
// 与根反例 fixture 同源:UO724 HKG→HKT 2027-07-17(bookable_exact_date)。
|
|
37
|
+
const map = JSON.parse(await import('node:fs/promises').then(m => m.readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8')));
|
|
38
|
+
const flight = factsFromFlyai({ kind: 'flight', origin: '香港', destination: '普吉', date: '2027-07-17' }, {
|
|
39
|
+
verdict: 'hit',
|
|
40
|
+
options: [{
|
|
41
|
+
no: 'UO724', name: '香港快运航空',
|
|
42
|
+
depDateTime: '2027-07-17T07:55:00', arrDateTime: '2027-07-17T10:30:00',
|
|
43
|
+
depStation: 'HKG', arrStation: 'HKT',
|
|
44
|
+
price: 793, nonstop: true,
|
|
45
|
+
}],
|
|
46
|
+
}, FETCHED, map.city_alias)[0];
|
|
47
|
+
const hotel = factsFromHotel({
|
|
48
|
+
source: 'flyai-hotel', destination: '大理',
|
|
49
|
+
checkIn: '2026-10-01', checkOut: '2026-10-03',
|
|
50
|
+
verdict: 'hit', options: 9, evidence: 'synthetic', fetchedAt: HOTEL_FETCHED,
|
|
51
|
+
})[0];
|
|
52
|
+
const policy = {
|
|
53
|
+
schema: BOOKABLE_FACT_SCHEMA,
|
|
54
|
+
fact_id: makeFactId(['policy-363', 'reg', '泰国免签']),
|
|
55
|
+
kind: 'policy',
|
|
56
|
+
subject: '泰国入境(中国护照)',
|
|
57
|
+
statement: '免签停留(口径以泰方公告为准);UAE 居民返程应优先校验 residence visa / Emirates ID 而非游客免签口径',
|
|
58
|
+
source: 'web:official',
|
|
59
|
+
query_id: 'web:policy:泰国免签',
|
|
60
|
+
fetched_at: FETCHED,
|
|
61
|
+
as_of: '2026-08-29',
|
|
62
|
+
};
|
|
63
|
+
// 走真实事实日志路径(append-only JSONL),与产品 gotry_flyai_search /
|
|
64
|
+
// gotry_session_search 同款事实落账接口;闸侧 loadFactRegistry 直接读这里。
|
|
65
|
+
await appendFacts(stateRoot, [flight, hotel, policy]);
|
|
66
|
+
// 装载 gotry-tools 插件 → 捕获 gotry_fact_gate 的真实 execute。
|
|
67
|
+
const registered = [];
|
|
68
|
+
const ctx = {
|
|
69
|
+
tools: { register: (t) => registered.push(t) },
|
|
70
|
+
systemPrompt: { section: () => undefined, variable: () => undefined },
|
|
71
|
+
on: () => () => undefined,
|
|
72
|
+
};
|
|
73
|
+
apply(ctx, {
|
|
74
|
+
stateRoot,
|
|
75
|
+
timeoutMs: 30_000,
|
|
76
|
+
hbcliBin: 'hbcli-not-on-path',
|
|
77
|
+
sessionAccess: 'off',
|
|
78
|
+
});
|
|
79
|
+
const factGate = registered.find(t => t.name === 'gotry_fact_gate');
|
|
80
|
+
if (!factGate)
|
|
81
|
+
throw new Error(`FAIL: 注册定义列表缺少 gotry_fact_gate,实际注册: ${registered.map(t => t.name).join(',')}`);
|
|
82
|
+
const exec = { agent: { session: { header: { cwd } } } };
|
|
83
|
+
const flightCanonical = renderFlightFact(flight);
|
|
84
|
+
const hotelCanonical = renderHotelFact(hotel);
|
|
85
|
+
// Case 1:canonical flight 行经真实 gotry_fact_gate → pass
|
|
86
|
+
const r1 = await factGate.execute({ markdown: ['## 航班', flightCanonical].join('\n') }, exec);
|
|
87
|
+
assert.equal(r1.verdict, 'pass', `canonical flight 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r1.verdict, summary: r1.summary })}`);
|
|
88
|
+
assert.equal(r1.traceable, 1);
|
|
89
|
+
console.log(' ok - 注册 gotry_fact_gate canonical flight → pass');
|
|
90
|
+
// Case 2:canonical hotel 行经真实 gotry_fact_gate → pass
|
|
91
|
+
const r2 = await factGate.execute({ markdown: ['## 住宿', hotelCanonical].join('\n') }, exec);
|
|
92
|
+
assert.equal(r2.verdict, 'pass', `canonical hotel 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r2.verdict, summary: r2.summary })}`);
|
|
93
|
+
assert.equal(r2.traceable, 1);
|
|
94
|
+
console.log(' ok - 注册 gotry_fact_gate canonical hotel → pass');
|
|
95
|
+
// Case 3:flight_no 改写(UO724 → UO999),保留 anchor → blocked/fact_anchor_unknown
|
|
96
|
+
const tamperedFlight = flightCanonical.replace('UO724', 'UO999');
|
|
97
|
+
const r3 = await factGate.execute({ markdown: ['## 航班', tamperedFlight].join('\n') }, exec);
|
|
98
|
+
assert.equal(r3.verdict, 'blocked', `flight_no 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r3.verdict, summary: r3.summary })}`);
|
|
99
|
+
assert.equal(r3.traceable, 0);
|
|
100
|
+
assert.ok(r3.violations?.some(v => v.kind === 'fact_anchor_unknown' && /UO999|UO724/.test(JSON.stringify(v))), `flight_no 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r3.violations?.map(v => v.kind))}`);
|
|
101
|
+
console.log(' ok - 注册 gotry_fact_gate flight_no UO724→UO999 → blocked/fact_anchor_unknown');
|
|
102
|
+
// Case 4:hotel destination 改写(大理 → 丽江)→ blocked/fact_anchor_unknown
|
|
103
|
+
const tamperedHotelDest = hotelCanonical.replace('大理', '丽江');
|
|
104
|
+
const r4 = await factGate.execute({ markdown: ['## 住宿', tamperedHotelDest].join('\n') }, exec);
|
|
105
|
+
assert.equal(r4.verdict, 'blocked', `hotel destination 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r4.verdict, summary: r4.summary })}`);
|
|
106
|
+
assert.ok(r4.violations?.some(v => v.kind === 'fact_anchor_unknown'), `hotel destination 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r4.violations?.map(v => v.kind))}`);
|
|
107
|
+
console.log(' ok - 注册 gotry_fact_gate hotel destination 大理→丽江 → blocked/fact_anchor_unknown');
|
|
108
|
+
// Case 5:hotel date 改写(2026-10-01 → 2026-12-25)→ blocked/fact_anchor_unknown
|
|
109
|
+
const tamperedHotelDate = hotelCanonical.replace('2026-10-01', '2026-12-25');
|
|
110
|
+
const r5 = await factGate.execute({ markdown: ['## 住宿', tamperedHotelDate].join('\n') }, exec);
|
|
111
|
+
assert.equal(r5.verdict, 'blocked', `hotel date 改写经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r5.verdict, summary: r5.summary })}`);
|
|
112
|
+
assert.equal(r5.traceable, 0);
|
|
113
|
+
assert.ok(r5.violations?.some(v => v.kind === 'fact_anchor_unknown'), `hotel date 改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r5.violations?.map(v => v.kind))}`);
|
|
114
|
+
console.log(' ok - 注册 gotry_fact_gate hotel date 2026-10-01→2026-12-25 → blocked/fact_anchor_unknown');
|
|
115
|
+
// Case 6:§4b 价格兼容——尾随总预算 → 不被 fact_anchor_unknown 吞并
|
|
116
|
+
const appendedBudget = `${flightCanonical};总预算¥1000`;
|
|
117
|
+
const r6 = await factGate.execute({ markdown: ['## 航班', appendedBudget].join('\n') }, exec);
|
|
118
|
+
assert.equal(r6.verdict, 'pass', `尾随总预算经注册闸应 pass,实际: ${JSON.stringify({ verdict: r6.verdict, summary: r6.summary })}`);
|
|
119
|
+
assert.ok(!r6.violations?.some(v => v.kind === 'fact_anchor_unknown'), `尾随总预算不应有 fact_anchor_unknown,实际: ${JSON.stringify(r6.violations?.map(v => v.kind))}`);
|
|
120
|
+
console.log(' ok - 注册 gotry_fact_gate flight §4b 价格兼容(尾随总预算)→ pass');
|
|
121
|
+
// Case 7:§4b 价格兼容——证据链前裸金额(¥999)→ 走 unverified_price_claim,
|
|
122
|
+
// 不退化为 fact_anchor_unknown
|
|
123
|
+
const bareAmount = flightCanonical.replace(' [flyai@', ';¥999 [flyai@');
|
|
124
|
+
const r7 = await factGate.execute({ markdown: ['## 航班', bareAmount].join('\n') }, exec);
|
|
125
|
+
assert.equal(r7.verdict, 'blocked', `证据链前裸金额经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r7.verdict, summary: r7.summary })}`);
|
|
126
|
+
assert.ok(r7.violations?.some(v => v.kind === 'unverified_price_claim'), `证据链前裸金额应有 unverified_price_claim,实际: ${JSON.stringify(r7.violations?.map(v => v.kind))}`);
|
|
127
|
+
assert.ok(!r7.violations?.some(v => v.kind === 'fact_anchor_unknown'), `证据链前裸金额不应有 fact_anchor_unknown,实际: ${JSON.stringify(r7.violations?.map(v => v.kind))}`);
|
|
128
|
+
console.log(' ok - 注册 gotry_fact_gate flight §4b 价格兼容(证据链前裸金额)→ blocked/unverified_price_claim');
|
|
129
|
+
// Case 8:§4b 价格矛盾(¥999 替换 ¥793)→ price_contradicted 分类保留,
|
|
130
|
+
// 不退化为 fact_anchor_unknown
|
|
131
|
+
const contradicted = flightCanonical.replace('¥793', '¥999');
|
|
132
|
+
const r8 = await factGate.execute({ markdown: ['## 航班', contradicted].join('\n') }, exec);
|
|
133
|
+
assert.equal(r8.verdict, 'blocked', `价格矛盾经注册闸应 blocked,实际: ${JSON.stringify({ verdict: r8.verdict, summary: r8.summary })}`);
|
|
134
|
+
assert.ok(r8.violations?.some(v => v.kind === 'price_contradicted'), `价格矛盾应有 price_contradicted,实际: ${JSON.stringify(r8.violations?.map(v => v.kind))}`);
|
|
135
|
+
assert.ok(!r8.violations?.some(v => v.kind === 'fact_anchor_unknown'), `价格矛盾不应有 fact_anchor_unknown,实际: ${JSON.stringify(r8.violations?.map(v => v.kind))}`);
|
|
136
|
+
console.log(' ok - 注册 gotry_fact_gate flight §4b 价格矛盾 → blocked/price_contradicted(分类保留)');
|
|
137
|
+
// Case 9:整行改写(flight_no + 价格)→ fact_anchor_unknown(字段级别 fail-closed 优先)
|
|
138
|
+
const allTampered = flightCanonical.replace('UO724', 'UO999').replace('¥793', '¥1000');
|
|
139
|
+
const r9 = await factGate.execute({ markdown: ['## 航班', allTampered].join('\n') }, exec);
|
|
140
|
+
assert.ok(r9.violations?.some(v => v.kind === 'fact_anchor_unknown'), `flight 字段 + 价格联合改写应有 fact_anchor_unknown,实际: ${JSON.stringify(r9.violations?.map(v => v.kind))}`);
|
|
141
|
+
console.log(' ok - 注册 gotry_fact_gate flight 字段+价格联合改写 → fact_anchor_unknown');
|
|
142
|
+
// Case 10:借用合法 fact_id 写入乱码 flight_no → fact_anchor_unknown
|
|
143
|
+
const borrowedAnchor = `## 航班\n- HKG→HKT 2027-07-17 XX9999(未知) 07:55→10:30 直飞 ¥793 [flyai@${flight.fetched_at} #${flight.query_id}] <!-- fact:${flight.fact_id} -->`;
|
|
144
|
+
const r10 = await factGate.execute({ markdown: borrowedAnchor }, exec);
|
|
145
|
+
assert.ok(r10.violations?.some(v => v.kind === 'fact_anchor_unknown'), `借用合法 fact_id 写入乱码 flight_no 应有 fact_anchor_unknown,实际: ${JSON.stringify(r10.violations?.map(v => v.kind))}`);
|
|
146
|
+
console.log(' ok - 注册 gotry_fact_gate 借用合法 fact_id + 乱码 flight_no → fact_anchor_unknown');
|
|
147
|
+
// Case 11:列车 canonical 形态保留——列车 kind 不走 flight_no 字段指纹
|
|
148
|
+
const gTrain = factsFromFlyai({ kind: 'train', origin: '香港', destination: '普吉', date: '2027-07-17' }, { verdict: 'hit', options: [{ no: 'G1234', depDateTime: '2027-07-17T09:00:00', arrDateTime: '2027-07-17T13:00:00' }] }, FETCHED, map.city_alias)[0];
|
|
149
|
+
await appendFacts(stateRoot, [gTrain]);
|
|
150
|
+
const trainRendered = renderFlightFact(gTrain);
|
|
151
|
+
const r11 = await factGate.execute({ markdown: ['## 车次', trainRendered].join('\n') }, exec);
|
|
152
|
+
assert.equal(r11.verdict, 'pass', `train canonical 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r11.verdict, summary: r11.summary })}`);
|
|
153
|
+
assert.ok(!r11.violations?.some(v => v.kind === 'fact_anchor_unknown'), `train canonical 不应有 fact_anchor_unknown,实际: ${JSON.stringify(r11.violations?.map(v => v.kind))}`);
|
|
154
|
+
console.log(' ok - 注册 gotry_fact_gate train canonical → pass(列车/航班分类独立,不被字段指纹吞并)');
|
|
155
|
+
// Case 12:政策锚点形态保留——政策行不受 flight_no/destination/check_in 字段指纹影响
|
|
156
|
+
const policyLine = renderPolicyFact(policy);
|
|
157
|
+
const r12 = await factGate.execute({ markdown: ['## 政策', policyLine].join('\n') }, exec);
|
|
158
|
+
assert.equal(r12.verdict, 'pass', `policy canonical 经注册闸应 pass,实际: ${JSON.stringify({ verdict: r12.verdict, summary: r12.summary })}`);
|
|
159
|
+
assert.ok(!r12.violations?.some(v => v.kind === 'fact_anchor_unknown'), `policy canonical 不应有 fact_anchor_unknown,实际: ${JSON.stringify(r12.violations?.map(v => v.kind))}`);
|
|
160
|
+
console.log(' ok - 注册 gotry_fact_gate policy canonical → pass(政策不受新字段指纹影响)');
|
|
161
|
+
// Case 13:借用合法 fact_id 写入伪造酒店 destination → blocked/fact_anchor_unknown
|
|
162
|
+
const borrowedHotel = `## 住宿\n- 丽江 2026-10-01→2026-10-03:住宿已核验(在架 9 家;价格上游打码,真实价以落地页为准) <!-- fact:${hotel.fact_id} -->`;
|
|
163
|
+
const r13 = await factGate.execute({ markdown: borrowedHotel }, exec);
|
|
164
|
+
assert.ok(r13.violations?.some(v => v.kind === 'fact_anchor_unknown'), `借用合法 hotel fact_id 写入伪造 destination 应有 fact_anchor_unknown,实际: ${JSON.stringify(r13.violations?.map(v => v.kind))}`);
|
|
165
|
+
console.log(' ok - 注册 gotry_fact_gate 借用合法 hotel fact_id + 伪造 destination → fact_anchor_unknown');
|
|
166
|
+
rmSync(home, { recursive: true, force: true });
|
|
167
|
+
console.log('\nFLIGHT/HOTEL ANCHOR #363 REGISTERED E2E: pass (deterministic synthetic, isolated stateRoot, registered gotry_fact_gate execute(), no host ToolRuntime / no live inventory)');
|
|
168
|
+
}
|
|
169
|
+
await main();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flight malformed fixture (#279):
|
|
3
|
+
* 隔离扩展传输车道:注入临时端口桥 + 进程内假扩展客户端(回环直 fetch)。
|
|
4
|
+
* 证明 sessionFlightSearch 的扩展分支对以下 batchSearch 响应都按合同收果:
|
|
5
|
+
* - JSON null body → verdict=error(非合法空,不静默收敛为 miss)
|
|
6
|
+
* - 非数组 flightItineraryList(对象) → verdict=error
|
|
7
|
+
* - 非数组 priceList(item 局部非法) → verdict=error(不暴露畸形行)
|
|
8
|
+
* - 合法空(data.flightItineraryList=[])→ verdict=miss(已识别合法空)
|
|
9
|
+
* - 合法命中 → verdict=hit
|
|
10
|
+
* - 挑战页(title 命 CHALLENGE_RE) → verdict=challenged(红线优先于解析)
|
|
11
|
+
* - 混合有效+畸形行 → verdict=hit(保留有效项)
|
|
12
|
+
* 全程:不开浏览器、不碰登录态、不写共享状态;唯一慢例=扩展会话桥自检(no-spend)。
|
|
13
|
+
*/
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { createSessionBridge, __resetSessionBridgeForTest, __setSessionBridgeForTest, EXTENSION_ORIGIN } from '../capabilities/session/extension-bridge.js';
|
|
19
|
+
import { sessionFlightSearch, __resetRateLimiterForTest } from '../capabilities/session-search.js';
|
|
20
|
+
import { buildEntryUrl } from '../capabilities/session/adapters/ctrip-flight.js';
|
|
21
|
+
let passed = 0;
|
|
22
|
+
async function check(label, fn) {
|
|
23
|
+
await fn();
|
|
24
|
+
passed += 1;
|
|
25
|
+
console.log(` ok - ${label}`);
|
|
26
|
+
}
|
|
27
|
+
async function claimOnce(port, respond) {
|
|
28
|
+
const r = await fetch(`http://127.0.0.1:${port}/jobs`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'content-type': 'application/json', origin: EXTENSION_ORIGIN },
|
|
31
|
+
body: JSON.stringify({ extensionVersion: 'flight-malformed-test', capabilities: ['ctrip-flight'] }),
|
|
32
|
+
});
|
|
33
|
+
const data = (await r.json());
|
|
34
|
+
if (data.job && respond) {
|
|
35
|
+
const result = respond(data.job);
|
|
36
|
+
if (result) {
|
|
37
|
+
await fetch(`http://127.0.0.1:${port}/results/${encodeURIComponent(data.job.jobId)}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'content-type': 'application/json', origin: EXTENSION_ORIGIN },
|
|
40
|
+
body: JSON.stringify(result),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
async function heartbeat(port) {
|
|
47
|
+
await fetch(`http://127.0.0.1:${port}/health`, { headers: { origin: EXTENSION_ORIGIN } });
|
|
48
|
+
}
|
|
49
|
+
async function main() {
|
|
50
|
+
console.log('§279 flight-malformed fixture(扩展传输车道注入,全离线)');
|
|
51
|
+
// 入口 URL 校验;不查时报 unresolved 错误(这是早于解析的闸)
|
|
52
|
+
const entry = buildEntryUrl('上海', '丽江', '2026-12-01');
|
|
53
|
+
assert.ok(entry.ok && entry.url);
|
|
54
|
+
const url = entry.url;
|
|
55
|
+
// 隔离临时目录(本测试不写真实 profile)
|
|
56
|
+
const tmp = mkdtempSync(join(tmpdir(), 'gotry-flight-malformed-'));
|
|
57
|
+
const bridge = await createSessionBridge({ ports: [0] });
|
|
58
|
+
assert.ok(bridge.ok, `临时桥创建成功: ${bridge.ok ? '' : bridge.summary}`);
|
|
59
|
+
if (!bridge.ok) {
|
|
60
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const port = bridge.bridge.port;
|
|
64
|
+
__setSessionBridgeForTest(bridge.bridge);
|
|
65
|
+
await heartbeat(port);
|
|
66
|
+
// 单次长跑假扩展客户端:
|
|
67
|
+
// cookie-names → 票据名(用户已登录态)
|
|
68
|
+
// search → 用当前 testCase.body/testCase.title 回包
|
|
69
|
+
// testCase 是可变闭包;每次 expectVerdict 改写它后调用 sessionFlightSearch。
|
|
70
|
+
let testCase = { body: '', title: '' };
|
|
71
|
+
let done = false;
|
|
72
|
+
const claimLoop = (async () => {
|
|
73
|
+
while (!done) {
|
|
74
|
+
const ac = new AbortController();
|
|
75
|
+
const bail = setTimeout(() => ac.abort(), 1_500);
|
|
76
|
+
try {
|
|
77
|
+
await claimOnce(port, (job) => {
|
|
78
|
+
if (job.kind === 'cookie-names')
|
|
79
|
+
return { ok: true, kind: 'cookie-names', names: ['cticket'] };
|
|
80
|
+
if (job.kind === 'search')
|
|
81
|
+
return { ok: true, kind: 'search', body: testCase.body, title: testCase.title };
|
|
82
|
+
return null;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch { /* park 中止=常态 */ }
|
|
86
|
+
clearTimeout(bail);
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
try {
|
|
90
|
+
const expectVerdict = async (label, body, title, expected, extra) => {
|
|
91
|
+
__resetRateLimiterForTest();
|
|
92
|
+
testCase = { body, title };
|
|
93
|
+
const r = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 5_000 });
|
|
94
|
+
await check(label, () => {
|
|
95
|
+
assert.equal(r.verdict, expected, `verdict 期望 ${expected},实 ${r.verdict} (${r.error ?? ''})`);
|
|
96
|
+
if (extra)
|
|
97
|
+
extra(r);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
await expectVerdict('JSON null body → error(非合法空,不静默收敛为 miss)', 'null', '机票列表', 'error', (r) => {
|
|
101
|
+
assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
|
|
102
|
+
assert.ok(!r.options, 'error 不带 options');
|
|
103
|
+
assert.ok(r.evidence.includes('[会话:ctrip-flight@error@'), '证据链为错误形');
|
|
104
|
+
});
|
|
105
|
+
await expectVerdict('非数组 flightItineraryList(对象) → error', JSON.stringify({ data: { flightItineraryList: { foo: 1 } } }), '机票列表', 'error', (r) => {
|
|
106
|
+
assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
|
|
107
|
+
});
|
|
108
|
+
await expectVerdict('非数组 flightItineraryList(字符串) → error', JSON.stringify({ data: { flightItineraryList: 'abc' } }), '机票列表', 'error');
|
|
109
|
+
await expectVerdict('非数组 priceList(item 局部非法) → error(不暴露畸形行)', JSON.stringify({ data: { flightItineraryList: [{
|
|
110
|
+
flightSegments: [{ airlineName: '吉祥航空', duration: 200, flightList: [{ flightNo: 'HO1', departureAirportName: '虹桥', arrivalAirportName: '三义', departureDateTime: '2026-12-01 07:35:00', arrivalDateTime: '2026-12-01 10:55:00' }] }],
|
|
111
|
+
priceList: 'not-an-array',
|
|
112
|
+
}] } }), '机票列表', 'error', (r) => {
|
|
113
|
+
assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
|
|
114
|
+
assert.ok(!r.options, 'error 不带 options');
|
|
115
|
+
assert.ok(r.evidence.includes('@error@'), '错误证据锚点存在');
|
|
116
|
+
});
|
|
117
|
+
await expectVerdict('合法空(flightItineraryList=[]) → miss(已识别合法空)', JSON.stringify({ data: { flightItineraryList: [] } }), '机票列表', 'miss', (r) => {
|
|
118
|
+
assert.equal(r.options?.length ?? 0, 0);
|
|
119
|
+
assert.ok(!r.error, '合法空不带 error');
|
|
120
|
+
assert.ok(r.evidence.includes('[会话:ctrip-flight@'), '证据链正常');
|
|
121
|
+
});
|
|
122
|
+
const validHit = JSON.stringify({ data: { flightItineraryList: [{
|
|
123
|
+
flightSegments: [{ airlineName: '吉祥航空', duration: 200, flightList: [{ flightNo: 'HO5577', departureAirportName: '虹桥国际机场', arrivalAirportName: '三义机场', departureDateTime: '2026-12-01 07:35:00', arrivalDateTime: '2026-12-01 10:55:00' }] }],
|
|
124
|
+
priceList: [{ adultPrice: 3240 }, { adultPrice: 2980 }],
|
|
125
|
+
}] } });
|
|
126
|
+
await expectVerdict('合法命中 → hit(flightNo+最低价)', validHit, '机票列表', 'hit', (r) => {
|
|
127
|
+
assert.equal(r.options?.length, 1);
|
|
128
|
+
assert.equal(r.options?.[0]?.flightNo, 'HO5577');
|
|
129
|
+
assert.equal(r.options?.[0]?.price, 2980);
|
|
130
|
+
assert.ok(r.evidence.includes('1 options'), '证据链含选项数');
|
|
131
|
+
});
|
|
132
|
+
await expectVerdict('挑战页(title 命中 验证/滑块) → challenged(红线优先于解析)', 'null', '请完成滑块验证', 'challenged', (r) => {
|
|
133
|
+
assert.ok(r.error?.includes('风控') || r.error?.includes('验证码'));
|
|
134
|
+
});
|
|
135
|
+
await expectVerdict('行全畸形 + 非空数组 → error(不伪装成 miss)', JSON.stringify({ data: { flightItineraryList: [{ flightSegments: [{}] }, 'not-object', null] } }), '机票列表', 'error', (r) => {
|
|
136
|
+
assert.ok(r.error?.includes('形状异常'), 'error 文案点明形状异常');
|
|
137
|
+
assert.ok(!r.options, 'error 不带 options');
|
|
138
|
+
assert.ok(r.evidence.includes('@error@'), '错误证据锚点存在');
|
|
139
|
+
});
|
|
140
|
+
await expectVerdict('混合有效+畸形行 → hit(保留有效项,price=0)', JSON.stringify({ data: { flightItineraryList: [
|
|
141
|
+
{ flightSegments: [{}], priceList: [{ adultPrice: 'bad' }] },
|
|
142
|
+
{ flightSegments: [{ flightList: [{ flightNo: 'HO2', departureDateTime: '2026-12-01 08:00:00' }] }], priceList: [] },
|
|
143
|
+
] } }), '机票列表', 'hit', (r) => {
|
|
144
|
+
assert.equal(r.options?.length, 1);
|
|
145
|
+
assert.equal(r.options?.[0]?.flightNo, 'HO2');
|
|
146
|
+
assert.equal(r.options?.[0]?.price, 0);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
done = true;
|
|
151
|
+
await claimLoop;
|
|
152
|
+
await __resetSessionBridgeForTest();
|
|
153
|
+
__setSessionBridgeForTest(null);
|
|
154
|
+
await bridge.bridge.close();
|
|
155
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
console.log(`\n§279 flight-malformed fixture: ${passed} 段全绿`);
|
|
158
|
+
}
|
|
159
|
+
await main();
|