@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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFF-only typed HTTP/SSE seam proof.
|
|
3
|
+
*
|
|
4
|
+
* Run with Node 24 from the repository root:
|
|
5
|
+
* npx --yes --package=node@24 --package=tsx --call \
|
|
6
|
+
* 'tsx ts/scripts/booking-copilot-server-proof-tests.ts'
|
|
7
|
+
*/
|
|
1
8
|
import assert from 'node:assert/strict';
|
|
2
9
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
10
|
import { tmpdir } from 'node:os';
|
|
@@ -14,20 +21,19 @@ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-server-'));
|
|
|
14
21
|
const ledger = ensureLedger(stateRoot);
|
|
15
22
|
let nextId = 0;
|
|
16
23
|
const runtime = new BookingCopilotTaskRuntime(ledger, {
|
|
17
|
-
idFactory: (prefix)
|
|
18
|
-
now: ()=>'2026-08-30T12:00:00.000Z'
|
|
24
|
+
idFactory: (prefix) => `${prefix}-${++nextId}`,
|
|
25
|
+
now: () => '2026-08-30T12:00:00.000Z',
|
|
19
26
|
});
|
|
20
27
|
let factoryCalls = 0;
|
|
21
28
|
let sessionTurns = 0;
|
|
22
|
-
const plannerFactory = (initialTask)=>{
|
|
29
|
+
const plannerFactory = (initialTask) => {
|
|
23
30
|
factoryCalls += 1;
|
|
24
31
|
assert.equal(initialTask.taskId, 'task-http-1');
|
|
25
32
|
return {
|
|
26
|
-
async next
|
|
33
|
+
async next({ task }) {
|
|
27
34
|
sessionTurns += 1;
|
|
28
35
|
if (sessionTurns === 1) {
|
|
29
|
-
return [
|
|
30
|
-
{
|
|
36
|
+
return [{
|
|
31
37
|
kind: 'operation',
|
|
32
38
|
action: {
|
|
33
39
|
schemaVersion: 'booking.surface',
|
|
@@ -37,36 +43,28 @@ const plannerFactory = (initialTask)=>{
|
|
|
37
43
|
expectedRevision: task.revision,
|
|
38
44
|
reason: 'Run the authoritative workspace search',
|
|
39
45
|
factRefs: [],
|
|
40
|
-
input: {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
];
|
|
46
|
+
input: {},
|
|
47
|
+
},
|
|
48
|
+
}];
|
|
44
49
|
}
|
|
45
50
|
return [
|
|
46
51
|
{
|
|
47
52
|
kind: 'explanation',
|
|
48
|
-
explanation: {
|
|
49
|
-
text: '{"kind":"search.run","input":{}}',
|
|
50
|
-
factRefs: []
|
|
51
|
-
}
|
|
53
|
+
explanation: { text: '{"kind":"search.run","input":{}}', factRefs: [] },
|
|
52
54
|
},
|
|
53
55
|
{
|
|
54
56
|
kind: 'terminal',
|
|
55
|
-
terminal: {
|
|
56
|
-
|
|
57
|
-
summary: 'Stopped at the requested search result.',
|
|
58
|
-
factRefs: []
|
|
59
|
-
}
|
|
60
|
-
}
|
|
57
|
+
terminal: { status: 'completed', summary: 'Stopped at the requested search result.', factRefs: [] },
|
|
58
|
+
},
|
|
61
59
|
];
|
|
62
|
-
}
|
|
60
|
+
},
|
|
63
61
|
};
|
|
64
62
|
};
|
|
65
63
|
const serverHandle = await startBookingCopilotServer({
|
|
66
64
|
apiKey: API_KEY,
|
|
67
65
|
runtime,
|
|
68
66
|
plannerFactory,
|
|
69
|
-
artifactId: ARTIFACT_ID
|
|
67
|
+
artifactId: ARTIFACT_ID,
|
|
70
68
|
});
|
|
71
69
|
const endpoint = `http://127.0.0.1:${serverHandle.port}/a2a/booking-copilot/turn`;
|
|
72
70
|
const expectedProbe = {
|
|
@@ -74,10 +72,7 @@ const expectedProbe = {
|
|
|
74
72
|
schemaSha256: SCHEMA_SHA256,
|
|
75
73
|
status: 'ready',
|
|
76
74
|
ingressMode: 'bff-bound-turn-only',
|
|
77
|
-
acceptedTurnKinds: [
|
|
78
|
-
'user.turn',
|
|
79
|
-
'action.receipt.continuation'
|
|
80
|
-
]
|
|
75
|
+
acceptedTurnKinds: ['user.turn', 'action.receipt.continuation'],
|
|
81
76
|
};
|
|
82
77
|
const runtimeReport = process.report?.getReport?.();
|
|
83
78
|
const runtimeGlibcVersion = runtimeReport?.header?.glibcVersionRuntime ?? '';
|
|
@@ -86,29 +81,15 @@ await assert.rejects(startBookingCopilotServer({
|
|
|
86
81
|
apiKey: API_KEY,
|
|
87
82
|
runtime,
|
|
88
83
|
plannerFactory,
|
|
89
|
-
artifactId: 'not-a-commit'
|
|
84
|
+
artifactId: 'not-a-commit',
|
|
90
85
|
}), /booking_copilot_artifact_id_invalid/);
|
|
91
|
-
for (const path of [
|
|
92
|
-
'/healthz',
|
|
93
|
-
'/status'
|
|
94
|
-
]){
|
|
86
|
+
for (const path of ['/healthz', '/status']) {
|
|
95
87
|
const probeUrl = `http://127.0.0.1:${serverHandle.port}${path}`;
|
|
96
|
-
for (const authorization of [
|
|
97
|
-
undefined
|
|
98
|
-
'Bearer wrong-key'
|
|
99
|
-
]){
|
|
100
|
-
const rejected = await fetch(probeUrl, authorization ? {
|
|
101
|
-
headers: {
|
|
102
|
-
authorization
|
|
103
|
-
}
|
|
104
|
-
} : undefined);
|
|
88
|
+
for (const authorization of [undefined, 'Bearer wrong-key']) {
|
|
89
|
+
const rejected = await fetch(probeUrl, authorization ? { headers: { authorization } } : undefined);
|
|
105
90
|
assert.equal(rejected.status, 401, `${path} rejects ${authorization ? 'wrong' : 'missing'} bearer`);
|
|
106
91
|
}
|
|
107
|
-
const authorized = await fetch(probeUrl, {
|
|
108
|
-
headers: {
|
|
109
|
-
authorization: `Bearer ${API_KEY}`
|
|
110
|
-
}
|
|
111
|
-
});
|
|
92
|
+
const authorized = await fetch(probeUrl, { headers: { authorization: `Bearer ${API_KEY}` } });
|
|
112
93
|
assert.equal(authorized.status, 200, `${path} accepts deployment bearer`);
|
|
113
94
|
assert.equal(authorized.headers.get('x-booking-surface-version'), SCHEMA_VERSION, `${path} returns canonical schema version header`);
|
|
114
95
|
assert.equal(authorized.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256, `${path} returns canonical schema hash header`);
|
|
@@ -127,18 +108,14 @@ const workspace = {
|
|
|
127
108
|
locale: 'zh-CN',
|
|
128
109
|
currency: 'AED',
|
|
129
110
|
searchDraft: {},
|
|
130
|
-
results: {
|
|
131
|
-
status: 'idle'
|
|
132
|
-
},
|
|
111
|
+
results: { status: 'idle' },
|
|
133
112
|
visibleHotels: [],
|
|
134
113
|
loadedOffers: [],
|
|
135
114
|
shortlistedOfferRefs: [],
|
|
136
115
|
capabilities: {
|
|
137
116
|
surface: 'tenant',
|
|
138
|
-
allowedActions: [
|
|
139
|
-
|
|
140
|
-
]
|
|
141
|
-
}
|
|
117
|
+
allowedActions: ['search.run'],
|
|
118
|
+
},
|
|
142
119
|
};
|
|
143
120
|
const userTurn = {
|
|
144
121
|
schemaVersion: 'booking.surface',
|
|
@@ -146,36 +123,27 @@ const userTurn = {
|
|
|
146
123
|
taskId: 'task-http-1',
|
|
147
124
|
turnId: 'http-turn-1',
|
|
148
125
|
workspace,
|
|
149
|
-
request: {
|
|
150
|
-
text: '执行当前搜索'
|
|
151
|
-
}
|
|
126
|
+
request: { text: '执行当前搜索' },
|
|
152
127
|
};
|
|
153
128
|
const post = (body, authorization = `Bearer ${API_KEY}`, schemaHeaders = {
|
|
154
129
|
'x-booking-surface-version': SCHEMA_VERSION,
|
|
155
|
-
'x-booking-surface-schema-sha256': SCHEMA_SHA256
|
|
156
|
-
})=>fetch(endpoint, {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
accept: 'text/event-stream',
|
|
162
|
-
...schemaHeaders
|
|
163
|
-
},
|
|
164
|
-
body: JSON.stringify(body)
|
|
165
|
-
});
|
|
130
|
+
'x-booking-surface-schema-sha256': SCHEMA_SHA256,
|
|
131
|
+
}) => fetch(endpoint, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { authorization, 'content-type': 'application/json', accept: 'text/event-stream', ...schemaHeaders },
|
|
134
|
+
body: JSON.stringify(body),
|
|
135
|
+
});
|
|
166
136
|
const noAuth = await fetch(endpoint, {
|
|
167
137
|
method: 'POST',
|
|
168
|
-
headers: {
|
|
169
|
-
|
|
170
|
-
},
|
|
171
|
-
body: JSON.stringify(userTurn)
|
|
138
|
+
headers: { 'content-type': 'application/json' },
|
|
139
|
+
body: JSON.stringify(userTurn),
|
|
172
140
|
});
|
|
173
141
|
assert.equal(noAuth.status, 401, 'server-to-server key is fail-closed');
|
|
174
142
|
const missingSchemaHeaders = await post(userTurn, `Bearer ${API_KEY}`, {});
|
|
175
143
|
assert.equal(missingSchemaHeaders.status, 409, 'schema version/hash headers are required');
|
|
176
144
|
const wrongSchemaHash = await post(userTurn, `Bearer ${API_KEY}`, {
|
|
177
145
|
'x-booking-surface-version': SCHEMA_VERSION,
|
|
178
|
-
'x-booking-surface-schema-sha256': '0'.repeat(64)
|
|
146
|
+
'x-booking-surface-schema-sha256': '0'.repeat(64),
|
|
179
147
|
});
|
|
180
148
|
assert.equal(wrongSchemaHash.status, 409, 'schema drift is rejected before planning');
|
|
181
149
|
const directIngress = await post({
|
|
@@ -184,28 +152,14 @@ const directIngress = await post({
|
|
|
184
152
|
requestKey: 'browser-request-1',
|
|
185
153
|
surfaceHint: 'tenant',
|
|
186
154
|
workspace: {
|
|
187
|
-
schemaVersion: 'booking.surface',
|
|
188
|
-
|
|
189
|
-
locale: 'zh-CN',
|
|
190
|
-
currency: 'AED',
|
|
191
|
-
searchDraft: {},
|
|
192
|
-
results: {
|
|
193
|
-
status: 'idle'
|
|
194
|
-
},
|
|
195
|
-
visibleHotels: [],
|
|
196
|
-
loadedOffers: [],
|
|
197
|
-
shortlistedOfferRefs: []
|
|
155
|
+
schemaVersion: 'booking.surface', revision: 0, locale: 'zh-CN', currency: 'AED',
|
|
156
|
+
searchDraft: {}, results: { status: 'idle' }, visibleHotels: [], loadedOffers: [], shortlistedOfferRefs: [],
|
|
198
157
|
},
|
|
199
|
-
request: {
|
|
200
|
-
text: 'browser must use same-origin BFF'
|
|
201
|
-
}
|
|
158
|
+
request: { text: 'browser must use same-origin BFF' },
|
|
202
159
|
});
|
|
203
160
|
assert.equal(directIngress.status, 503, 'GoTry server rejects unbound browser ingress');
|
|
204
161
|
assert.equal((await directIngress.json()).error.code, 'trusted_ingress_binding_required');
|
|
205
|
-
const tokenSmuggle = await post({
|
|
206
|
-
...userTurn,
|
|
207
|
-
portalToken: 'ST:must-not-cross-boundary'
|
|
208
|
-
});
|
|
162
|
+
const tokenSmuggle = await post({ ...userTurn, portalToken: 'ST:must-not-cross-boundary' });
|
|
209
163
|
assert.equal(tokenSmuggle.status, 400, 'strict planner turn rejects portal token fields');
|
|
210
164
|
const first = await post(userTurn);
|
|
211
165
|
assert.equal(first.status, 200);
|
|
@@ -213,21 +167,16 @@ assert.match(String(first.headers.get('content-type')), /text\/event-stream/);
|
|
|
213
167
|
assert.equal(first.headers.get('x-booking-surface-version'), SCHEMA_VERSION);
|
|
214
168
|
assert.equal(first.headers.get('x-booking-surface-schema-sha256'), SCHEMA_SHA256);
|
|
215
169
|
function parseSse(body) {
|
|
216
|
-
return body.trim().split('\n\n').map((frame)=>{
|
|
217
|
-
const data = frame.split('\n').find((line)=>line.startsWith('data: '));
|
|
170
|
+
return body.trim().split('\n\n').map((frame) => {
|
|
171
|
+
const data = frame.split('\n').find((line) => line.startsWith('data: '));
|
|
218
172
|
assert.ok(data, `SSE frame has data: ${frame}`);
|
|
219
173
|
return JSON.parse(data.slice('data: '.length));
|
|
220
174
|
});
|
|
221
175
|
}
|
|
222
176
|
const firstEvents = parseSse(await first.text());
|
|
223
|
-
assert.deepEqual(firstEvents.map((event)=>event.kind), [
|
|
224
|
-
'status',
|
|
225
|
-
'status',
|
|
226
|
-
'operation',
|
|
227
|
-
'status'
|
|
228
|
-
]);
|
|
177
|
+
assert.deepEqual(firstEvents.map((event) => event.kind), ['status', 'status', 'operation', 'status']);
|
|
229
178
|
assert.equal(firstEvents[3].kind === 'status' ? firstEvents[3].status : '', 'waiting_receipt', 'the operation leaves the task durably waiting for its receipt');
|
|
230
|
-
assert.ok(firstEvents.every((event)=>event.contextRef === workspace.contextRef), 'every typed event carries BFF-minted contextRef');
|
|
179
|
+
assert.ok(firstEvents.every((event) => event.contextRef === workspace.contextRef), 'every typed event carries BFF-minted contextRef');
|
|
231
180
|
const operation = firstEvents[2];
|
|
232
181
|
assert.equal(operation?.kind, 'operation');
|
|
233
182
|
assert.equal(factoryCalls, 1);
|
|
@@ -239,11 +188,7 @@ const receiptTurn = {
|
|
|
239
188
|
workspace: {
|
|
240
189
|
...workspace,
|
|
241
190
|
revision: 1,
|
|
242
|
-
results: {
|
|
243
|
-
status: 'ready',
|
|
244
|
-
resultCount: 2,
|
|
245
|
-
searchSessionRef: 'search-http-1'
|
|
246
|
-
}
|
|
191
|
+
results: { status: 'ready', resultCount: 2, searchSessionRef: 'search-http-1' },
|
|
247
192
|
},
|
|
248
193
|
receipt: {
|
|
249
194
|
schemaVersion: 'booking.surface',
|
|
@@ -252,37 +197,22 @@ const receiptTurn = {
|
|
|
252
197
|
contextRef: workspace.contextRef,
|
|
253
198
|
status: 'applied',
|
|
254
199
|
revision: 1,
|
|
255
|
-
observation: {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
resultCount: 2
|
|
259
|
-
},
|
|
260
|
-
resultContract: {
|
|
261
|
-
outcome: 'complete',
|
|
262
|
-
hardCriteriaMet: true,
|
|
263
|
-
factRefs: [],
|
|
264
|
-
gapCodes: [],
|
|
265
|
-
blockers: [],
|
|
266
|
-
relaxationsApplied: []
|
|
267
|
-
}
|
|
268
|
-
}
|
|
200
|
+
observation: { kind: 'search.state', searchSessionRef: 'search-http-1', resultCount: 2 },
|
|
201
|
+
resultContract: { outcome: 'complete', hardCriteriaMet: true, factRefs: [], gapCodes: [], blockers: [], relaxationsApplied: [] },
|
|
202
|
+
},
|
|
269
203
|
};
|
|
270
204
|
const second = await post(receiptTurn);
|
|
271
205
|
assert.equal(second.status, 200);
|
|
272
206
|
const secondEvents = parseSse(await second.text());
|
|
273
|
-
assert.deepEqual(secondEvents.map((event)=>event.kind), [
|
|
274
|
-
'status',
|
|
275
|
-
'explanation',
|
|
276
|
-
'terminal'
|
|
277
|
-
]);
|
|
207
|
+
assert.deepEqual(secondEvents.map((event) => event.kind), ['status', 'explanation', 'terminal']);
|
|
278
208
|
assert.equal(factoryCalls, 1, 'one task reuses one planner session across receipt continuations');
|
|
279
209
|
assert.equal(sessionTurns, 2);
|
|
280
|
-
assert.ok(!secondEvents.some((event)=>event.kind === 'operation'), 'JSON-looking explanation text is not parsed into an operation');
|
|
210
|
+
assert.ok(!secondEvents.some((event) => event.kind === 'operation'), 'JSON-looking explanation text is not parsed into an operation');
|
|
281
211
|
assert.ok(secondEvents[0].sequence > firstEvents.at(-1).sequence, 'SSE sequence is monotonic across turns');
|
|
282
212
|
await serverHandle.close();
|
|
283
213
|
const preRestartRuntime = new BookingCopilotTaskRuntime(ledger, {
|
|
284
|
-
idFactory: (prefix)
|
|
285
|
-
now: ()=>'2026-08-30T12:01:00.000Z'
|
|
214
|
+
idFactory: (prefix) => `${prefix}-probe-${++nextId}`,
|
|
215
|
+
now: () => '2026-08-30T12:01:00.000Z',
|
|
286
216
|
});
|
|
287
217
|
const recovered = preRestartRuntime.resumeTask('task-http-1');
|
|
288
218
|
assert.equal(recovered?.lastReceipt?.actionId, 'action-http-1', 'new server process seam restores task from last typed receipt');
|
|
@@ -290,23 +220,10 @@ assert.equal(recovered?.phase, 'terminal', 'the completed task stays durably ter
|
|
|
290
220
|
const restarted = await startBookingCopilotServer({
|
|
291
221
|
apiKey: API_KEY,
|
|
292
222
|
runtime: preRestartRuntime,
|
|
293
|
-
plannerFactory: (initialTask)=>{
|
|
223
|
+
plannerFactory: (initialTask) => {
|
|
294
224
|
assert.equal(initialTask.taskId, 'task-http-2');
|
|
295
|
-
return {
|
|
296
|
-
|
|
297
|
-
return [
|
|
298
|
-
{
|
|
299
|
-
kind: 'terminal',
|
|
300
|
-
terminal: {
|
|
301
|
-
status: 'stopped',
|
|
302
|
-
summary: 'Recovered.',
|
|
303
|
-
factRefs: []
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
];
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
}
|
|
225
|
+
return { async next() { return [{ kind: 'terminal', terminal: { status: 'stopped', summary: 'Recovered.', factRefs: [] } }]; } };
|
|
226
|
+
},
|
|
310
227
|
});
|
|
311
228
|
const restartedEndpoint = `http://127.0.0.1:${restarted.port}/a2a/booking-copilot/turn`;
|
|
312
229
|
const afterRestartResponse = await fetch(restartedEndpoint, {
|
|
@@ -316,37 +233,15 @@ const afterRestartResponse = await fetch(restartedEndpoint, {
|
|
|
316
233
|
'content-type': 'application/json',
|
|
317
234
|
accept: 'text/event-stream',
|
|
318
235
|
'x-booking-surface-version': SCHEMA_VERSION,
|
|
319
|
-
'x-booking-surface-schema-sha256': SCHEMA_SHA256
|
|
236
|
+
'x-booking-surface-schema-sha256': SCHEMA_SHA256,
|
|
320
237
|
},
|
|
321
|
-
body: JSON.stringify({
|
|
322
|
-
...userTurn,
|
|
323
|
-
taskId: 'task-http-2',
|
|
324
|
-
turnId: 'http-restart-turn-1',
|
|
325
|
-
workspace: {
|
|
326
|
-
...workspace,
|
|
327
|
-
contextRef: 'ctx-http-2'
|
|
328
|
-
}
|
|
329
|
-
})
|
|
238
|
+
body: JSON.stringify({ ...userTurn, taskId: 'task-http-2', turnId: 'http-restart-turn-1', workspace: { ...workspace, contextRef: 'ctx-http-2' } }),
|
|
330
239
|
});
|
|
331
240
|
assert.equal(afterRestartResponse.status, 200);
|
|
332
241
|
const restartEvents = parseSse(await afterRestartResponse.text());
|
|
333
|
-
assert.deepEqual(restartEvents.map((event)=>event.kind), [
|
|
334
|
-
|
|
335
|
-
'status',
|
|
336
|
-
'terminal'
|
|
337
|
-
]);
|
|
338
|
-
assert.deepEqual(restartEvents.map((event)=>event.sequence), [
|
|
339
|
-
1,
|
|
340
|
-
2,
|
|
341
|
-
3
|
|
342
|
-
], 'the restarted process allocates fresh task-scoped sequences after recovering the ledger');
|
|
242
|
+
assert.deepEqual(restartEvents.map((event) => event.kind), ['status', 'status', 'terminal']);
|
|
243
|
+
assert.deepEqual(restartEvents.map((event) => event.sequence), [1, 2, 3], 'the restarted process allocates fresh task-scoped sequences after recovering the ledger');
|
|
343
244
|
await restarted.close();
|
|
344
245
|
ledger.close();
|
|
345
|
-
rmSync(stateRoot, {
|
|
346
|
-
recursive: true,
|
|
347
|
-
force: true
|
|
348
|
-
});
|
|
246
|
+
rmSync(stateRoot, { recursive: true, force: true });
|
|
349
247
|
console.log('BOOKING COPILOT SERVER PROOF: BFF auth/typed SSE/task session/restart/no-token/no-text-action OK');
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
//# sourceURL=ts/scripts/booking-copilot-server-proof-tests.ts
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
/** Production startup composition proof (dependencies injected; no real model). */
|
|
1
2
|
import assert from 'node:assert/strict';
|
|
2
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
|
-
import { resolveBookingCopilotStartupConfig, startBookingCopilotFromEnvironment } from '../src/booking-surface/startup.js';
|
|
6
|
+
import { resolveBookingCopilotStartupConfig, startBookingCopilotFromEnvironment, } from '../src/booking-surface/startup.js';
|
|
6
7
|
import { BOOKING_READ_ACTION_KINDS } from '../src/booking-surface/contracts.js';
|
|
7
|
-
assert.throws(()=>resolveBookingCopilotStartupConfig({}), /booking_copilot_api_key_required/);
|
|
8
|
-
assert.throws(()=>resolveBookingCopilotStartupConfig({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
assert.throws(()=>resolveBookingCopilotStartupConfig({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
assert.throws(() => resolveBookingCopilotStartupConfig({}), /booking_copilot_api_key_required/);
|
|
9
|
+
assert.throws(() => resolveBookingCopilotStartupConfig({
|
|
10
|
+
GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
|
|
11
|
+
GOTRY_BOOKING_COPILOT_STATE_ROOT: '/tmp/gotry-booking-startup',
|
|
12
|
+
GOTRY_BOOKING_COPILOT_ARTIFACT_ID: 'not-a-commit',
|
|
13
|
+
}), /booking_copilot_artifact_id_invalid/);
|
|
14
|
+
assert.throws(() => resolveBookingCopilotStartupConfig({
|
|
15
|
+
GOTRY_BOOKING_COPILOT_API_KEY: 'bff-key',
|
|
16
|
+
GOTRY_BOOKING_COPILOT_STATE_ROOT: 'relative/state',
|
|
17
|
+
}), /booking_copilot_state_root_must_be_absolute/);
|
|
17
18
|
const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-'));
|
|
18
19
|
const env = {
|
|
19
20
|
GOTRY_BOOKING_COPILOT_API_KEY: 'bff-only-key',
|
|
@@ -22,134 +23,57 @@ const env = {
|
|
|
22
23
|
GOTRY_BOOKING_COPILOT_ARTIFACT_ID: '1111111111111111111111111111111111111111',
|
|
23
24
|
LLM_API_KEY: 'model-only-key',
|
|
24
25
|
PORTAL_TOKEN: 'must-not-enter-planner',
|
|
25
|
-
HOTELBYTE_TOKEN: 'must-not-enter-planner'
|
|
26
|
+
HOTELBYTE_TOKEN: 'must-not-enter-planner',
|
|
26
27
|
};
|
|
27
|
-
await assert.rejects(startBookingCopilotFromEnvironment({
|
|
28
|
-
...env,
|
|
29
|
-
GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding'
|
|
30
|
-
}, {}), /booking_copilot_ingress_binding_required/, 'explicit ingress mode rejects startup when the trusted BFF seam is absent');
|
|
28
|
+
await assert.rejects(startBookingCopilotFromEnvironment({ ...env, GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding' }, {}), /booking_copilot_ingress_binding_required/, 'explicit ingress mode rejects startup when the trusted BFF seam is absent');
|
|
31
29
|
await assert.rejects(startBookingCopilotFromEnvironment(env, {
|
|
32
|
-
ingressBinding: {
|
|
33
|
-
bind: ()=>({
|
|
34
|
-
taskId: 'partial-task',
|
|
35
|
-
turnId: 'partial-turn',
|
|
36
|
-
contextRef: 'partial-context',
|
|
37
|
-
surface: 'tenant',
|
|
38
|
-
allowedActions: [
|
|
39
|
-
...BOOKING_READ_ACTION_KINDS
|
|
40
|
-
]
|
|
41
|
-
})
|
|
42
|
-
}
|
|
30
|
+
ingressBinding: { bind: () => ({ taskId: 'partial-task', turnId: 'partial-turn', contextRef: 'partial-context', surface: 'tenant', allowedActions: [...BOOKING_READ_ACTION_KINDS] }) },
|
|
43
31
|
}), /booking_copilot_ingress_binding_pair_required/, 'partial trusted BFF seam is rejected before startup');
|
|
44
|
-
await assert.rejects(startBookingCopilotFromEnvironment(env, {
|
|
45
|
-
principal: {
|
|
46
|
-
subject: 'partial-bff',
|
|
47
|
-
scope: 'booking:read'
|
|
48
|
-
}
|
|
49
|
-
}), /booking_copilot_ingress_binding_pair_required/, 'principal without a binding is rejected before startup');
|
|
32
|
+
await assert.rejects(startBookingCopilotFromEnvironment(env, { principal: { subject: 'partial-bff', scope: 'booking:read' } }), /booking_copilot_ingress_binding_pair_required/, 'principal without a binding is rejected before startup');
|
|
50
33
|
let standaloneIngressMode = '';
|
|
51
34
|
const standaloneStateRoot = mkdtempSync(join(tmpdir(), 'gotry-booking-startup-standalone-'));
|
|
52
|
-
const standalone = await startBookingCopilotFromEnvironment({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, {
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
close () {}
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
runtimeFactory () {
|
|
62
|
-
return {};
|
|
63
|
-
},
|
|
64
|
-
createPlanner: async ()=>({
|
|
65
|
-
plannerFactory () {
|
|
66
|
-
return {
|
|
67
|
-
async next () {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
async close () {}
|
|
73
|
-
}),
|
|
74
|
-
async startServer (options) {
|
|
35
|
+
const standalone = await startBookingCopilotFromEnvironment({ ...env, GOTRY_BOOKING_COPILOT_STATE_ROOT: standaloneStateRoot }, {
|
|
36
|
+
ensureLedger() { return { close() { } }; },
|
|
37
|
+
runtimeFactory() { return {}; },
|
|
38
|
+
createPlanner: async () => ({ plannerFactory() { return { async next() { return []; } }; }, async close() { } }),
|
|
39
|
+
async startServer(options) {
|
|
75
40
|
standaloneIngressMode = options.ingressMode ?? '';
|
|
76
|
-
return {
|
|
77
|
-
|
|
78
|
-
port: 43122,
|
|
79
|
-
async close () {}
|
|
80
|
-
};
|
|
81
|
-
}
|
|
41
|
+
return { server: {}, port: 43122, async close() { } };
|
|
42
|
+
},
|
|
82
43
|
});
|
|
83
44
|
assert.equal(standalone.port, 43122);
|
|
84
45
|
assert.equal(standaloneIngressMode, 'bff-bound-turn-only', 'standalone production startup uses the explicit bound-turn-only mode');
|
|
85
46
|
await standalone.close();
|
|
86
|
-
rmSync(standaloneStateRoot, {
|
|
87
|
-
recursive: true,
|
|
88
|
-
force: true
|
|
89
|
-
});
|
|
47
|
+
rmSync(standaloneStateRoot, { recursive: true, force: true });
|
|
90
48
|
const closeOrder = [];
|
|
91
49
|
let plannerEnv;
|
|
92
50
|
let serverApiKey = '';
|
|
93
51
|
let serverArtifactId = '';
|
|
94
|
-
const trustedEnv = {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
98
|
-
const fakeLedger = {
|
|
99
|
-
close () {
|
|
100
|
-
closeOrder.push('ledger');
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
const trustedBinding = {
|
|
104
|
-
bind: ()=>({
|
|
105
|
-
taskId: 'startup-task',
|
|
106
|
-
turnId: 'startup-turn',
|
|
107
|
-
contextRef: 'startup-context',
|
|
108
|
-
surface: 'tenant',
|
|
109
|
-
allowedActions: [
|
|
110
|
-
...BOOKING_READ_ACTION_KINDS
|
|
111
|
-
]
|
|
112
|
-
})
|
|
113
|
-
};
|
|
52
|
+
const trustedEnv = { ...env, GOTRY_BOOKING_COPILOT_INGRESS_MODE: 'bff-ingress-binding' };
|
|
53
|
+
const fakeLedger = { close() { closeOrder.push('ledger'); } };
|
|
54
|
+
const trustedBinding = { bind: () => ({ taskId: 'startup-task', turnId: 'startup-turn', contextRef: 'startup-context', surface: 'tenant', allowedActions: [...BOOKING_READ_ACTION_KINDS] }) };
|
|
114
55
|
const started = await startBookingCopilotFromEnvironment(trustedEnv, {
|
|
115
|
-
ensureLedger
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
runtimeFactory () {
|
|
119
|
-
return {};
|
|
120
|
-
},
|
|
121
|
-
async createPlanner (options) {
|
|
56
|
+
ensureLedger() { return fakeLedger; },
|
|
57
|
+
runtimeFactory() { return {}; },
|
|
58
|
+
async createPlanner(options) {
|
|
122
59
|
plannerEnv = options.env;
|
|
123
60
|
return {
|
|
124
|
-
plannerFactory () {
|
|
125
|
-
|
|
126
|
-
async next () {
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
},
|
|
131
|
-
async close () {
|
|
132
|
-
closeOrder.push('planner');
|
|
133
|
-
}
|
|
61
|
+
plannerFactory() { return { async next() { return []; } }; },
|
|
62
|
+
async close() { closeOrder.push('planner'); },
|
|
134
63
|
};
|
|
135
64
|
},
|
|
136
65
|
ingressBinding: trustedBinding,
|
|
137
|
-
principal: {
|
|
138
|
-
|
|
139
|
-
scope: 'booking:read'
|
|
140
|
-
},
|
|
141
|
-
async startServer (options) {
|
|
66
|
+
principal: { subject: 'startup-bff', scope: 'booking:read' },
|
|
67
|
+
async startServer(options) {
|
|
142
68
|
serverApiKey = options.apiKey;
|
|
143
69
|
serverArtifactId = options.artifactId ?? '';
|
|
144
70
|
assert.equal(options.ingressMode, 'bff-ingress-binding');
|
|
145
71
|
return {
|
|
146
72
|
server: {},
|
|
147
73
|
port: 43123,
|
|
148
|
-
async close ()
|
|
149
|
-
closeOrder.push('server');
|
|
150
|
-
}
|
|
74
|
+
async close() { closeOrder.push('server'); },
|
|
151
75
|
};
|
|
152
|
-
}
|
|
76
|
+
},
|
|
153
77
|
});
|
|
154
78
|
assert.equal(started.port, 43123);
|
|
155
79
|
assert.equal(serverApiKey, 'bff-only-key', 'BFF deployment key terminates at the HTTP server');
|
|
@@ -159,16 +83,6 @@ assert.equal(plannerEnv?.GOTRY_BOOKING_COPILOT_API_KEY, undefined);
|
|
|
159
83
|
assert.equal(plannerEnv?.PORTAL_TOKEN, undefined);
|
|
160
84
|
assert.equal(plannerEnv?.HOTELBYTE_TOKEN, undefined);
|
|
161
85
|
await started.close();
|
|
162
|
-
assert.deepEqual(closeOrder, [
|
|
163
|
-
|
|
164
|
-
'planner',
|
|
165
|
-
'ledger'
|
|
166
|
-
]);
|
|
167
|
-
rmSync(stateRoot, {
|
|
168
|
-
recursive: true,
|
|
169
|
-
force: true
|
|
170
|
-
});
|
|
86
|
+
assert.deepEqual(closeOrder, ['server', 'planner', 'ledger']);
|
|
87
|
+
rmSync(stateRoot, { recursive: true, force: true });
|
|
171
88
|
console.log('BOOKING COPILOT STARTUP PROOF: env split/server+planner+ledger lifecycle OK');
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//# sourceURL=ts/scripts/booking-copilot-startup-proof-tests.ts
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { closeBackend, createBackendServer } from '../src/backend/kernel.js';
|
|
2
|
+
import { startBookingExecutorModule } from '../src/backend/modules/booking-executor.js';
|
|
3
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4
|
+
let pass = 0;
|
|
5
|
+
let fail = 0;
|
|
6
|
+
const check = (cond, label) => {
|
|
7
|
+
if (cond) {
|
|
8
|
+
pass += 1;
|
|
9
|
+
console.log(` ok - ${label}`);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
fail += 1;
|
|
13
|
+
console.log(` FAIL - ${label}`);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
async function jfetch(url, init) {
|
|
17
|
+
const r = await fetch(url, init);
|
|
18
|
+
let body = null;
|
|
19
|
+
try {
|
|
20
|
+
body = await r.json();
|
|
21
|
+
}
|
|
22
|
+
catch { /* 空体 */ }
|
|
23
|
+
return { status: r.status, body };
|
|
24
|
+
}
|
|
25
|
+
async function main() {
|
|
26
|
+
const module = startBookingExecutorModule({
|
|
27
|
+
apiKey: () => 'test-booking-key',
|
|
28
|
+
evidenceDir: () => '/tmp/booking-executor-test-evidence',
|
|
29
|
+
observe: async () => ({
|
|
30
|
+
ok: true,
|
|
31
|
+
supplier: 'dida-portal',
|
|
32
|
+
entryUrl: 'https://portal.dida.com/hotel/find',
|
|
33
|
+
entryPoints: [{ text: '预订', tag: 'button' }],
|
|
34
|
+
screenshotPath: '/tmp/evidence/test.png',
|
|
35
|
+
observedAt: new Date().toISOString(),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
const handle = await createBackendServer({ modules: [module], port: 0 });
|
|
39
|
+
const base = `http://127.0.0.1:${handle.port}`;
|
|
40
|
+
try {
|
|
41
|
+
// ① 鉴权 fail-closed
|
|
42
|
+
const noAuth = await jfetch(`${base}/v1/booking/observe`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'content-type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({ supplier: 'dida-portal' }),
|
|
46
|
+
});
|
|
47
|
+
check(noAuth.status === 403, '无 token 403');
|
|
48
|
+
// ② 注入观察器:正常观察
|
|
49
|
+
const ok = await jfetch(`${base}/v1/booking/observe`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
|
|
52
|
+
body: JSON.stringify({ supplier: 'dida-portal' }),
|
|
53
|
+
});
|
|
54
|
+
const okBody = ok.body;
|
|
55
|
+
check(ok.status === 200 && okBody.ok === true && okBody.supplier === 'dida-portal', '观察返回 200 + 供应商');
|
|
56
|
+
check(Array.isArray(okBody.entryPoints) && okBody.entryPoints.length === 1, '入口观察条目形状');
|
|
57
|
+
// ③ 未知供应商拒绝
|
|
58
|
+
const bad = await jfetch(`${base}/v1/booking/observe`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
|
|
61
|
+
body: JSON.stringify({ supplier: 'ctrip-flight' }),
|
|
62
|
+
});
|
|
63
|
+
check(bad.status === 400, '未知供应商 400');
|
|
64
|
+
// ④ 域外 entryUrl 拒绝
|
|
65
|
+
const evil = await jfetch(`${base}/v1/booking/observe`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer test-booking-key' },
|
|
68
|
+
body: JSON.stringify({ supplier: 'dida-portal', entryUrl: 'https://evil.example.com/x' }),
|
|
69
|
+
});
|
|
70
|
+
check(evil.status === 400, '域外 entryUrl 400');
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await closeBackend(handle, [module]).catch(() => { });
|
|
74
|
+
}
|
|
75
|
+
await sleep(30);
|
|
76
|
+
console.log(`\nBOOKING EXECUTOR: ${pass} pass, ${fail} fail`);
|
|
77
|
+
if (fail > 0)
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
}
|
|
80
|
+
void main();
|