@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,1221 +1,206 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { BOOKING_READ_ACTION_KINDS, BOOKING_GAP_CODES,
|
|
5
|
-
import { validateBookingSurface,
|
|
4
|
+
import { BOOKING_READ_ACTION_KINDS, BOOKING_GAP_CODES, BOOKING_SURFACE_SCHEMA_SHA256, bookingSurfaceAllowedActions } from '../src/booking-surface/contracts.js';
|
|
5
|
+
import { validateBookingSurface, validateBookingSurfaceEvent, validateCriterionBlocker, validateRelaxationApproval, validateApprovalAgainstBlocker } from '../src/booking-surface/validation.js';
|
|
6
6
|
let positive = 0;
|
|
7
7
|
let negative = 0;
|
|
8
|
-
function accept(label, value) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function reject(label, value) {
|
|
13
|
-
assert.equal(validateBookingSurface(value).ok, false, label);
|
|
14
|
-
negative++;
|
|
15
|
-
}
|
|
16
|
-
assert.deepEqual(bookingSurfaceAllowedActions('storefront'), [
|
|
17
|
-
'search.patch',
|
|
18
|
-
'search.run',
|
|
19
|
-
'results.view.patch',
|
|
20
|
-
'hotel.focus'
|
|
21
|
-
], 'storefront UAT availability observation is least privilege');
|
|
22
|
-
assert.deepEqual(bookingSurfaceAllowedActions('payment_link'), [
|
|
23
|
-
'search.patch',
|
|
24
|
-
'search.run',
|
|
25
|
-
'results.view.patch',
|
|
26
|
-
'hotel.focus',
|
|
27
|
-
'hotel.select'
|
|
28
|
-
], 'payment_link UAT availability observation is least privilege');
|
|
8
|
+
function accept(label, value) { assert.equal(validateBookingSurface(value).ok, true, label); positive++; }
|
|
9
|
+
function reject(label, value) { assert.equal(validateBookingSurface(value).ok, false, label); negative++; }
|
|
10
|
+
assert.deepEqual(bookingSurfaceAllowedActions('storefront'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus'], 'storefront UAT availability observation is least privilege');
|
|
11
|
+
assert.deepEqual(bookingSurfaceAllowedActions('payment_link'), ['search.patch', 'search.run', 'results.view.patch', 'hotel.focus', 'hotel.select'], 'payment_link UAT availability observation is least privilege');
|
|
29
12
|
assert.ok(!bookingSurfaceAllowedActions('storefront').includes('offers.query'), 'storefront availability cannot expand into offers');
|
|
30
13
|
assert.ok(!bookingSurfaceAllowedActions('payment_link').includes('checkout.prepare'), 'payment_link availability cannot expand into checkout');
|
|
31
14
|
const hash = 'a'.repeat(64);
|
|
32
|
-
const workspace = {
|
|
33
|
-
|
|
34
|
-
contextRef: 'ctx-1',
|
|
35
|
-
surface: 'tenant',
|
|
36
|
-
revision: 0,
|
|
37
|
-
locale: 'zh-CN',
|
|
38
|
-
currency: 'AED',
|
|
39
|
-
searchDraft: {},
|
|
40
|
-
results: {
|
|
41
|
-
status: 'idle'
|
|
42
|
-
},
|
|
43
|
-
visibleHotels: [],
|
|
44
|
-
loadedOffers: [],
|
|
45
|
-
shortlistedOfferRefs: [],
|
|
46
|
-
capabilities: {
|
|
47
|
-
surface: 'tenant',
|
|
48
|
-
allowedActions: [
|
|
49
|
-
...BOOKING_READ_ACTION_KINDS
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const ingressWorkspace = {
|
|
54
|
-
...workspace
|
|
55
|
-
};
|
|
15
|
+
const workspace = { schemaVersion: 'booking.surface', contextRef: 'ctx-1', surface: 'tenant', revision: 0, locale: 'zh-CN', currency: 'AED', searchDraft: {}, results: { status: 'idle' }, visibleHotels: [], loadedOffers: [], shortlistedOfferRefs: [], capabilities: { surface: 'tenant', allowedActions: [...BOOKING_READ_ACTION_KINDS] } };
|
|
16
|
+
const ingressWorkspace = { ...workspace };
|
|
56
17
|
delete ingressWorkspace.contextRef;
|
|
57
18
|
delete ingressWorkspace.surface;
|
|
58
19
|
delete ingressWorkspace.capabilities;
|
|
59
20
|
const inputs = {
|
|
60
|
-
'search.patch': {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
'hotel.select': {
|
|
77
|
-
hotelRef: 'hotel-1'
|
|
78
|
-
},
|
|
79
|
-
'offers.query': {
|
|
80
|
-
hotelRefs: [
|
|
81
|
-
'hotel-1'
|
|
82
|
-
],
|
|
83
|
-
criteria: {}
|
|
84
|
-
},
|
|
85
|
-
'offers.view.patch': {
|
|
86
|
-
hotelRef: 'hotel-1',
|
|
87
|
-
criteria: {}
|
|
88
|
-
},
|
|
89
|
-
'offers.compare': {
|
|
90
|
-
offerRefs: [
|
|
91
|
-
'offer-1'
|
|
92
|
-
],
|
|
93
|
-
requestedCount: 1
|
|
94
|
-
},
|
|
95
|
-
'offer.select': {
|
|
96
|
-
offerRef: 'offer-1',
|
|
97
|
-
offerVersionRef: 'offer-1:v1'
|
|
98
|
-
},
|
|
99
|
-
'offer.check': {
|
|
100
|
-
offerRef: 'offer-1',
|
|
101
|
-
offerVersionRef: 'offer-1:v1'
|
|
102
|
-
},
|
|
103
|
-
'checkout.prepare': {
|
|
104
|
-
offerRef: 'offer-1',
|
|
105
|
-
offerVersionRef: 'offer-1:v1',
|
|
106
|
-
verifiedOfferRef: 'verified-1'
|
|
107
|
-
},
|
|
108
|
-
'order.observe': {
|
|
109
|
-
orderRef: 'order-1'
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const actions = Object.fromEntries(BOOKING_READ_ACTION_KINDS.map((kind)=>[
|
|
113
|
-
kind,
|
|
114
|
-
{
|
|
115
|
-
schemaVersion: 'booking.surface',
|
|
116
|
-
kind,
|
|
117
|
-
actionId: `a-${kind.replaceAll('.', '-')}`,
|
|
118
|
-
contextRef: 'ctx-1',
|
|
119
|
-
expectedRevision: 0,
|
|
120
|
-
reason: 'typed request',
|
|
121
|
-
factRefs: [],
|
|
122
|
-
input: inputs[kind]
|
|
123
|
-
}
|
|
124
|
-
]));
|
|
125
|
-
const approvalRef = {
|
|
126
|
-
approvalId: 'ap-1',
|
|
127
|
-
blockerId: 'b-1',
|
|
128
|
-
contextRef: 'ctx-1',
|
|
129
|
-
sourceTurnId: 'turn-1',
|
|
130
|
-
presentationRequestKey: 'approval:task-1:turn-1:a-search-run:' + hash,
|
|
131
|
-
sourceActionId: 'a-search-run',
|
|
132
|
-
targetActionId: 'a-search-run',
|
|
133
|
-
sourceRevision: 0,
|
|
134
|
-
targetActionKind: 'search.run',
|
|
135
|
-
to: 'prefer',
|
|
136
|
-
expiresAt: '2099-01-01T00:00:00.000Z',
|
|
137
|
-
nonce: 'nonce-1',
|
|
138
|
-
sourceReceiptDigest: hash,
|
|
139
|
-
scope: 'offer',
|
|
140
|
-
code: 'hotel_rates_failed',
|
|
141
|
-
criterionPath: 'offers.totalPriceMax',
|
|
142
|
-
valueDigest: hash
|
|
143
|
-
};
|
|
144
|
-
accept('valid relaxation approval ref action', {
|
|
145
|
-
...actions['search.run'],
|
|
146
|
-
relaxationApprovalRef: approvalRef
|
|
147
|
-
});
|
|
148
|
-
reject('approval context replay', {
|
|
149
|
-
...actions['search.run'],
|
|
150
|
-
relaxationApprovalRef: {
|
|
151
|
-
...approvalRef,
|
|
152
|
-
contextRef: 'ctx-other'
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
reject('approval action replay', {
|
|
156
|
-
...actions['search.run'],
|
|
157
|
-
relaxationApprovalRef: {
|
|
158
|
-
...approvalRef,
|
|
159
|
-
targetActionId: 'a-other'
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
reject('approval future revision replay', {
|
|
163
|
-
...actions['search.run'],
|
|
164
|
-
relaxationApprovalRef: {
|
|
165
|
-
...approvalRef,
|
|
166
|
-
sourceRevision: 1
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
reject('approval old revision replay', {
|
|
170
|
-
...actions['search.run'],
|
|
171
|
-
expectedRevision: 1,
|
|
172
|
-
relaxationApprovalRef: {
|
|
173
|
-
...approvalRef,
|
|
174
|
-
sourceRevision: 0
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
reject('approval target kind replay', {
|
|
178
|
-
...actions['search.run'],
|
|
179
|
-
relaxationApprovalRef: {
|
|
180
|
-
...approvalRef,
|
|
181
|
-
targetActionKind: 'offer.check'
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
reject('approval bad nonce', {
|
|
185
|
-
...actions['search.run'],
|
|
186
|
-
relaxationApprovalRef: {
|
|
187
|
-
...approvalRef,
|
|
188
|
-
nonce: 'Bearer token'
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
reject('approval bad expiry', {
|
|
192
|
-
...actions['search.run'],
|
|
193
|
-
relaxationApprovalRef: {
|
|
194
|
-
...approvalRef,
|
|
195
|
-
expiresAt: 'not-a-date'
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
const openInput = (kind)=>{
|
|
21
|
+
'search.patch': { patch: { destination: { query: 'Dubai' } } }, 'search.run': {}, 'results.view.patch': { patch: { sort: 'recommended' } }, 'hotel.focus': { hotelRef: 'hotel-1' }, 'hotel.select': { hotelRef: 'hotel-1' },
|
|
22
|
+
'offers.query': { hotelRefs: ['hotel-1'], criteria: {} }, 'offers.view.patch': { hotelRef: 'hotel-1', criteria: {} }, 'offers.compare': { offerRefs: ['offer-1'], requestedCount: 1 },
|
|
23
|
+
'offer.select': { offerRef: 'offer-1', offerVersionRef: 'offer-1:v1' }, 'offer.check': { offerRef: 'offer-1', offerVersionRef: 'offer-1:v1' }, 'checkout.prepare': { offerRef: 'offer-1', offerVersionRef: 'offer-1:v1', verifiedOfferRef: 'verified-1' }, 'order.observe': { orderRef: 'order-1' },
|
|
24
|
+
};
|
|
25
|
+
const actions = Object.fromEntries(BOOKING_READ_ACTION_KINDS.map((kind) => [kind, { schemaVersion: 'booking.surface', kind, actionId: `a-${kind.replaceAll('.', '-')}`, contextRef: 'ctx-1', expectedRevision: 0, reason: 'typed request', factRefs: [], input: inputs[kind] }]));
|
|
26
|
+
const approvalRef = { approvalId: 'ap-1', blockerId: 'b-1', contextRef: 'ctx-1', sourceTurnId: 'turn-1', presentationRequestKey: 'approval:task-1:turn-1:a-search-run:' + hash, sourceActionId: 'a-search-run', targetActionId: 'a-search-run', sourceRevision: 0, targetActionKind: 'search.run', to: 'prefer', expiresAt: '2099-01-01T00:00:00.000Z', nonce: 'nonce-1', sourceReceiptDigest: hash, scope: 'offer', code: 'hotel_rates_failed', criterionPath: 'offers.totalPriceMax', valueDigest: hash };
|
|
27
|
+
accept('valid relaxation approval ref action', { ...actions['search.run'], relaxationApprovalRef: approvalRef });
|
|
28
|
+
reject('approval context replay', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, contextRef: 'ctx-other' } });
|
|
29
|
+
reject('approval action replay', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, targetActionId: 'a-other' } });
|
|
30
|
+
reject('approval future revision replay', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, sourceRevision: 1 } });
|
|
31
|
+
reject('approval old revision replay', { ...actions['search.run'], expectedRevision: 1, relaxationApprovalRef: { ...approvalRef, sourceRevision: 0 } });
|
|
32
|
+
reject('approval target kind replay', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, targetActionKind: 'offer.check' } });
|
|
33
|
+
reject('approval bad nonce', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, nonce: 'Bearer token' } });
|
|
34
|
+
reject('approval bad expiry', { ...actions['search.run'], relaxationApprovalRef: { ...approvalRef, expiresAt: 'not-a-date' } });
|
|
35
|
+
const openInput = (kind) => {
|
|
199
36
|
const input = inputs[kind];
|
|
200
|
-
return input && typeof input === 'object' && !Array.isArray(input) ? {
|
|
201
|
-
...input,
|
|
202
|
-
open: true
|
|
203
|
-
} : {
|
|
204
|
-
open: true
|
|
205
|
-
};
|
|
37
|
+
return input && typeof input === 'object' && !Array.isArray(input) ? { ...input, open: true } : { open: true };
|
|
206
38
|
};
|
|
207
|
-
for (const kind of BOOKING_READ_ACTION_KINDS){
|
|
39
|
+
for (const kind of BOOKING_READ_ACTION_KINDS) {
|
|
208
40
|
accept(`valid action ${kind}`, actions[kind]);
|
|
209
|
-
reject(`missing input ${kind}`, {
|
|
210
|
-
|
|
211
|
-
input: undefined
|
|
212
|
-
});
|
|
213
|
-
reject(`open input ${kind}`, {
|
|
214
|
-
...actions[kind],
|
|
215
|
-
input: openInput(kind)
|
|
216
|
-
});
|
|
41
|
+
reject(`missing input ${kind}`, { ...actions[kind], input: undefined });
|
|
42
|
+
reject(`open input ${kind}`, { ...actions[kind], input: openInput(kind) });
|
|
217
43
|
}
|
|
218
|
-
reject('offer.select missing version', {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
offerRef: 'offer-1'
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
reject('offer.check missing version', {
|
|
225
|
-
...actions['offer.check'],
|
|
226
|
-
input: {
|
|
227
|
-
offerRef: 'offer-1'
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
reject('checkout.prepare missing version', {
|
|
231
|
-
...actions['checkout.prepare'],
|
|
232
|
-
input: {
|
|
233
|
-
offerRef: 'offer-1',
|
|
234
|
-
verifiedOfferRef: 'verified-1'
|
|
235
|
-
}
|
|
236
|
-
});
|
|
44
|
+
reject('offer.select missing version', { ...actions['offer.select'], input: { offerRef: 'offer-1' } });
|
|
45
|
+
reject('offer.check missing version', { ...actions['offer.check'], input: { offerRef: 'offer-1' } });
|
|
46
|
+
reject('checkout.prepare missing version', { ...actions['checkout.prepare'], input: { offerRef: 'offer-1', verifiedOfferRef: 'verified-1' } });
|
|
237
47
|
const versionedWorkspace = {
|
|
238
48
|
...workspace,
|
|
239
|
-
visibleHotels: [
|
|
240
|
-
|
|
241
|
-
hotelRef: 'hotel-1',
|
|
242
|
-
name: 'Hotel 1',
|
|
243
|
-
factRefs: [
|
|
244
|
-
'hotel-fact-1'
|
|
245
|
-
]
|
|
246
|
-
}
|
|
247
|
-
],
|
|
248
|
-
loadedOffers: [
|
|
249
|
-
{
|
|
250
|
-
offerRef: 'offer-1',
|
|
251
|
-
offerVersionRef: 'offer-1:v1',
|
|
252
|
-
hotelRef: 'hotel-1',
|
|
253
|
-
evidenceLevel: 'rate_loaded',
|
|
254
|
-
factRefs: [
|
|
255
|
-
'offer-fact-1'
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
],
|
|
49
|
+
visibleHotels: [{ hotelRef: 'hotel-1', name: 'Hotel 1', factRefs: ['hotel-fact-1'] }],
|
|
50
|
+
loadedOffers: [{ offerRef: 'offer-1', offerVersionRef: 'offer-1:v1', hotelRef: 'hotel-1', evidenceLevel: 'rate_loaded', factRefs: ['offer-fact-1'] }],
|
|
259
51
|
selectedOfferRef: 'offer-1',
|
|
260
|
-
verifiedOffer: {
|
|
261
|
-
offerRef: 'offer-1',
|
|
262
|
-
offerVersionRef: 'offer-1:v1',
|
|
263
|
-
verifiedOfferRef: 'verified-1',
|
|
264
|
-
expiresAt: '2030-02-28T23:59:59+04:00'
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
const versionedTurn = {
|
|
268
|
-
schemaVersion: 'booking.surface',
|
|
269
|
-
kind: 'user.turn',
|
|
270
|
-
taskId: 'versioned-task',
|
|
271
|
-
turnId: 'versioned-turn',
|
|
272
|
-
workspace: versionedWorkspace,
|
|
273
|
-
request: {
|
|
274
|
-
text: 'find hotels'
|
|
275
|
-
}
|
|
52
|
+
verifiedOffer: { offerRef: 'offer-1', offerVersionRef: 'offer-1:v1', verifiedOfferRef: 'verified-1', expiresAt: '2030-02-28T23:59:59+04:00' },
|
|
276
53
|
};
|
|
54
|
+
const versionedTurn = { schemaVersion: 'booking.surface', kind: 'user.turn', taskId: 'versioned-task', turnId: 'versioned-turn', workspace: versionedWorkspace, request: { text: 'find hotels' } };
|
|
277
55
|
accept('versioned loaded offer and verified tuple', versionedTurn);
|
|
278
|
-
reject('verified offer invalid calendar expiry', {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
});
|
|
288
|
-
reject('
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
...versionedWorkspace.verifiedOffer,
|
|
294
|
-
expiresAt: 'tomorrow'
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
reject('verified offer missing expiry', {
|
|
299
|
-
...versionedTurn,
|
|
300
|
-
workspace: {
|
|
301
|
-
...versionedWorkspace,
|
|
302
|
-
verifiedOffer: {
|
|
303
|
-
...versionedWorkspace.verifiedOffer,
|
|
304
|
-
expiresAt: undefined
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
reject('verified offer mismatched logical tuple', {
|
|
309
|
-
...versionedTurn,
|
|
310
|
-
workspace: {
|
|
311
|
-
...versionedWorkspace,
|
|
312
|
-
verifiedOffer: {
|
|
313
|
-
...versionedWorkspace.verifiedOffer,
|
|
314
|
-
offerRef: 'offer-other',
|
|
315
|
-
offerVersionRef: 'offer-other:v1'
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
reject('verified offer mismatched version tuple', {
|
|
320
|
-
...versionedTurn,
|
|
321
|
-
workspace: {
|
|
322
|
-
...versionedWorkspace,
|
|
323
|
-
verifiedOffer: {
|
|
324
|
-
...versionedWorkspace.verifiedOffer,
|
|
325
|
-
offerVersionRef: 'offer-1:v2'
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
reject('workspace duplicate logical offer refs', {
|
|
330
|
-
...versionedTurn,
|
|
331
|
-
workspace: {
|
|
332
|
-
...versionedWorkspace,
|
|
333
|
-
loadedOffers: [
|
|
334
|
-
...versionedWorkspace.loadedOffers,
|
|
335
|
-
{
|
|
336
|
-
...versionedWorkspace.loadedOffers[0]
|
|
337
|
-
}
|
|
338
|
-
]
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
reject('workspace duplicate offer version refs', {
|
|
342
|
-
...versionedTurn,
|
|
343
|
-
workspace: {
|
|
344
|
-
...versionedWorkspace,
|
|
345
|
-
loadedOffers: [
|
|
346
|
-
...versionedWorkspace.loadedOffers,
|
|
347
|
-
{
|
|
348
|
-
offerRef: 'offer-2',
|
|
349
|
-
offerVersionRef: 'offer-1:v1',
|
|
350
|
-
hotelRef: 'hotel-1',
|
|
351
|
-
evidenceLevel: 'rate_loaded',
|
|
352
|
-
factRefs: []
|
|
353
|
-
}
|
|
354
|
-
]
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
reject('Book', {
|
|
358
|
-
...actions['search.run'],
|
|
359
|
-
kind: 'book'
|
|
360
|
-
});
|
|
361
|
-
reject('root minimum', {
|
|
362
|
-
schemaVersion: 'booking.surface'
|
|
363
|
-
});
|
|
364
|
-
reject('workspace nested extra', {
|
|
365
|
-
...workspace,
|
|
366
|
-
results: {
|
|
367
|
-
status: 'idle',
|
|
368
|
-
open: true
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
reject('workspace capability Book', {
|
|
372
|
-
...workspace,
|
|
373
|
-
capabilities: {
|
|
374
|
-
...workspace.capabilities,
|
|
375
|
-
allowedActions: [
|
|
376
|
-
'book'
|
|
377
|
-
]
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
for (const field of [
|
|
381
|
-
'schemaVersion',
|
|
382
|
-
'kind',
|
|
383
|
-
'actionId',
|
|
384
|
-
'contextRef',
|
|
385
|
-
'expectedRevision'
|
|
386
|
-
])reject(`action missing ${field}`, Object.fromEntries(Object.entries(actions['search.run']).filter(([key])=>key !== field)));
|
|
387
|
-
const blocker = {
|
|
388
|
-
blockerId: 'b-1',
|
|
389
|
-
sourceActionId: 'a-offers-query',
|
|
390
|
-
sourceReceiptDigest: hash,
|
|
391
|
-
scope: 'offer',
|
|
392
|
-
code: 'hotel_rates_failed',
|
|
393
|
-
criterionPath: 'offers.totalPriceMax',
|
|
394
|
-
strength: 'must',
|
|
395
|
-
valueDigest: hash,
|
|
396
|
-
evidence: {
|
|
397
|
-
factRefs: [
|
|
398
|
-
'fact-1'
|
|
399
|
-
],
|
|
400
|
-
gapCodes: [
|
|
401
|
-
'hotel_rates_failed'
|
|
402
|
-
],
|
|
403
|
-
requested: 2,
|
|
404
|
-
actual: 1
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
const approval = {
|
|
408
|
-
taskId: 'task-1',
|
|
409
|
-
contextRef: 'ctx-1',
|
|
410
|
-
sourceTurnId: 'turn-1',
|
|
411
|
-
presentationRequestKey: 'approval:task-1:turn-1:a-offers-query:' + hash,
|
|
412
|
-
optionDigest: hash,
|
|
413
|
-
approvalId: 'ap-1',
|
|
414
|
-
deliveryNonce: 'delivery-nonce-1',
|
|
415
|
-
blockerId: 'b-1',
|
|
416
|
-
sourceActionId: blocker.sourceActionId,
|
|
417
|
-
sourceReceiptDigest: hash,
|
|
418
|
-
scope: 'offer',
|
|
419
|
-
code: blocker.code,
|
|
420
|
-
criterionPath: blocker.criterionPath,
|
|
421
|
-
valueDigest: hash,
|
|
422
|
-
from: 'must',
|
|
423
|
-
to: 'prefer',
|
|
424
|
-
approved: true
|
|
425
|
-
};
|
|
56
|
+
reject('verified offer invalid calendar expiry', { ...versionedTurn, workspace: { ...versionedWorkspace, verifiedOffer: { ...versionedWorkspace.verifiedOffer, expiresAt: '2026-02-30T00:00:00.000Z' } } });
|
|
57
|
+
reject('verified offer malformed expiry', { ...versionedTurn, workspace: { ...versionedWorkspace, verifiedOffer: { ...versionedWorkspace.verifiedOffer, expiresAt: 'tomorrow' } } });
|
|
58
|
+
reject('verified offer missing expiry', { ...versionedTurn, workspace: { ...versionedWorkspace, verifiedOffer: { ...versionedWorkspace.verifiedOffer, expiresAt: undefined } } });
|
|
59
|
+
reject('verified offer mismatched logical tuple', { ...versionedTurn, workspace: { ...versionedWorkspace, verifiedOffer: { ...versionedWorkspace.verifiedOffer, offerRef: 'offer-other', offerVersionRef: 'offer-other:v1' } } });
|
|
60
|
+
reject('verified offer mismatched version tuple', { ...versionedTurn, workspace: { ...versionedWorkspace, verifiedOffer: { ...versionedWorkspace.verifiedOffer, offerVersionRef: 'offer-1:v2' } } });
|
|
61
|
+
reject('workspace duplicate logical offer refs', { ...versionedTurn, workspace: { ...versionedWorkspace, loadedOffers: [...versionedWorkspace.loadedOffers, { ...versionedWorkspace.loadedOffers[0] }] } });
|
|
62
|
+
reject('workspace duplicate offer version refs', { ...versionedTurn, workspace: { ...versionedWorkspace, loadedOffers: [...versionedWorkspace.loadedOffers, { offerRef: 'offer-2', offerVersionRef: 'offer-1:v1', hotelRef: 'hotel-1', evidenceLevel: 'rate_loaded', factRefs: [] }] } });
|
|
63
|
+
reject('Book', { ...actions['search.run'], kind: 'book' });
|
|
64
|
+
reject('root minimum', { schemaVersion: 'booking.surface' });
|
|
65
|
+
reject('workspace nested extra', { ...workspace, results: { status: 'idle', open: true } });
|
|
66
|
+
reject('workspace capability Book', { ...workspace, capabilities: { ...workspace.capabilities, allowedActions: ['book'] } });
|
|
67
|
+
for (const field of ['schemaVersion', 'kind', 'actionId', 'contextRef', 'expectedRevision'])
|
|
68
|
+
reject(`action missing ${field}`, Object.fromEntries(Object.entries(actions['search.run']).filter(([key]) => key !== field)));
|
|
69
|
+
const blocker = { blockerId: 'b-1', sourceActionId: 'a-offers-query', sourceReceiptDigest: hash, scope: 'offer', code: 'hotel_rates_failed', criterionPath: 'offers.totalPriceMax', strength: 'must', valueDigest: hash, evidence: { factRefs: ['fact-1'], gapCodes: ['hotel_rates_failed'], requested: 2, actual: 1 } };
|
|
70
|
+
const approval = { taskId: 'task-1', contextRef: 'ctx-1', sourceTurnId: 'turn-1', presentationRequestKey: 'approval:task-1:turn-1:a-offers-query:' + hash, optionDigest: hash, approvalId: 'ap-1', deliveryNonce: 'delivery-nonce-1', blockerId: 'b-1', sourceActionId: blocker.sourceActionId, sourceReceiptDigest: hash, scope: 'offer', code: blocker.code, criterionPath: blocker.criterionPath, valueDigest: hash, from: 'must', to: 'prefer', approved: true };
|
|
426
71
|
assert.equal(validateCriterionBlocker(blocker).ok, true);
|
|
427
72
|
positive++;
|
|
428
73
|
assert.equal(validateRelaxationApproval(approval).ok, true);
|
|
429
74
|
positive++;
|
|
430
75
|
assert.equal(validateApprovalAgainstBlocker(approval, blocker).ok, true);
|
|
431
76
|
positive++;
|
|
432
|
-
for (const field of [
|
|
433
|
-
'
|
|
434
|
-
'valueDigest'
|
|
435
|
-
]){
|
|
436
|
-
assert.equal(validateCriterionBlocker({
|
|
437
|
-
...blocker,
|
|
438
|
-
[field]: 'bad'
|
|
439
|
-
}).ok, false);
|
|
77
|
+
for (const field of ['sourceReceiptDigest', 'valueDigest']) {
|
|
78
|
+
assert.equal(validateCriterionBlocker({ ...blocker, [field]: 'bad' }).ok, false);
|
|
440
79
|
negative++;
|
|
441
80
|
}
|
|
442
|
-
for (const field of [
|
|
443
|
-
'
|
|
444
|
-
'scope',
|
|
445
|
-
'criterionPath'
|
|
446
|
-
]){
|
|
447
|
-
assert.equal(validateCriterionBlocker({
|
|
448
|
-
...blocker,
|
|
449
|
-
[field]: field === 'criterionPath' ? 'open.path' : 'dynamic'
|
|
450
|
-
}).ok, false);
|
|
81
|
+
for (const field of ['code', 'scope', 'criterionPath']) {
|
|
82
|
+
assert.equal(validateCriterionBlocker({ ...blocker, [field]: field === 'criterionPath' ? 'open.path' : 'dynamic' }).ok, false);
|
|
451
83
|
negative++;
|
|
452
84
|
}
|
|
453
|
-
assert.equal(validateCriterionBlocker({
|
|
454
|
-
...blocker,
|
|
455
|
-
evidence: {
|
|
456
|
-
...blocker.evidence,
|
|
457
|
-
factRefs: [
|
|
458
|
-
'fact-1',
|
|
459
|
-
'fact-1'
|
|
460
|
-
]
|
|
461
|
-
}
|
|
462
|
-
}).ok, false);
|
|
85
|
+
assert.equal(validateCriterionBlocker({ ...blocker, evidence: { ...blocker.evidence, factRefs: ['fact-1', 'fact-1'] } }).ok, false);
|
|
463
86
|
negative++;
|
|
464
|
-
assert.equal(validateCriterionBlocker({
|
|
465
|
-
...blocker,
|
|
466
|
-
evidence: {
|
|
467
|
-
...blocker.evidence,
|
|
468
|
-
open: true
|
|
469
|
-
}
|
|
470
|
-
}).ok, false);
|
|
87
|
+
assert.equal(validateCriterionBlocker({ ...blocker, evidence: { ...blocker.evidence, open: true } }).ok, false);
|
|
471
88
|
negative++;
|
|
472
|
-
const receipt = {
|
|
473
|
-
schemaVersion: 'booking.surface',
|
|
474
|
-
kind: 'action.receipt',
|
|
475
|
-
actionId: 'a-search-run',
|
|
476
|
-
contextRef: 'ctx-1',
|
|
477
|
-
status: 'partial',
|
|
478
|
-
revision: 1,
|
|
479
|
-
observation: {
|
|
480
|
-
kind: 'gap',
|
|
481
|
-
code: 'search_failed',
|
|
482
|
-
factRefs: [
|
|
483
|
-
'fact-1'
|
|
484
|
-
]
|
|
485
|
-
},
|
|
486
|
-
resultContract: {
|
|
487
|
-
outcome: 'partial',
|
|
488
|
-
hardCriteriaMet: false,
|
|
489
|
-
factRefs: [
|
|
490
|
-
'fact-1'
|
|
491
|
-
],
|
|
492
|
-
gapCodes: [
|
|
493
|
-
...BOOKING_GAP_CODES
|
|
494
|
-
],
|
|
495
|
-
blockers: [
|
|
496
|
-
blocker
|
|
497
|
-
],
|
|
498
|
-
relaxationsApplied: []
|
|
499
|
-
}
|
|
500
|
-
};
|
|
89
|
+
const receipt = { schemaVersion: 'booking.surface', kind: 'action.receipt', actionId: 'a-search-run', contextRef: 'ctx-1', status: 'partial', revision: 1, observation: { kind: 'gap', code: 'search_failed', factRefs: ['fact-1'] }, resultContract: { outcome: 'partial', hardCriteriaMet: false, factRefs: ['fact-1'], gapCodes: [...BOOKING_GAP_CODES], blockers: [blocker], relaxationsApplied: [] } };
|
|
501
90
|
accept('valid receipt all gap codes', receipt);
|
|
502
|
-
reject('bad status', {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
});
|
|
506
|
-
reject('
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
});
|
|
510
|
-
reject('bad outcome', {
|
|
511
|
-
...receipt,
|
|
512
|
-
resultContract: {
|
|
513
|
-
...receipt.resultContract,
|
|
514
|
-
outcome: 'bad'
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
reject('dynamic gap', {
|
|
518
|
-
...receipt,
|
|
519
|
-
resultContract: {
|
|
520
|
-
...receipt.resultContract,
|
|
521
|
-
gapCodes: [
|
|
522
|
-
'dynamic'
|
|
523
|
-
]
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
reject('receipt observation extra', {
|
|
527
|
-
...receipt,
|
|
528
|
-
observation: {
|
|
529
|
-
...receipt.observation,
|
|
530
|
-
open: true
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
for (const code of BOOKING_GAP_CODES){
|
|
534
|
-
accept(`valid declared gap ${code}`, {
|
|
535
|
-
...receipt,
|
|
536
|
-
resultContract: {
|
|
537
|
-
...receipt.resultContract,
|
|
538
|
-
gapCodes: [
|
|
539
|
-
code
|
|
540
|
-
]
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
reject(`dynamic gap ${code}`, {
|
|
544
|
-
...receipt,
|
|
545
|
-
resultContract: {
|
|
546
|
-
...receipt.resultContract,
|
|
547
|
-
gapCodes: [
|
|
548
|
-
`${code}:dynamic`
|
|
549
|
-
]
|
|
550
|
-
}
|
|
551
|
-
});
|
|
91
|
+
reject('bad status', { ...receipt, status: 'unknown' });
|
|
92
|
+
reject('bad revision', { ...receipt, revision: -1 });
|
|
93
|
+
reject('bad outcome', { ...receipt, resultContract: { ...receipt.resultContract, outcome: 'bad' } });
|
|
94
|
+
reject('dynamic gap', { ...receipt, resultContract: { ...receipt.resultContract, gapCodes: ['dynamic'] } });
|
|
95
|
+
reject('receipt observation extra', { ...receipt, observation: { ...receipt.observation, open: true } });
|
|
96
|
+
for (const code of BOOKING_GAP_CODES) {
|
|
97
|
+
accept(`valid declared gap ${code}`, { ...receipt, resultContract: { ...receipt.resultContract, gapCodes: [code] } });
|
|
98
|
+
reject(`dynamic gap ${code}`, { ...receipt, resultContract: { ...receipt.resultContract, gapCodes: [`${code}:dynamic`] } });
|
|
552
99
|
}
|
|
553
100
|
const observations = [
|
|
554
|
-
{
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
},
|
|
559
|
-
{
|
|
560
|
-
kind: 'results.state',
|
|
561
|
-
matchedHotelRefs: [
|
|
562
|
-
'hotel-1'
|
|
563
|
-
],
|
|
564
|
-
visibleCount: 1
|
|
565
|
-
},
|
|
566
|
-
{
|
|
567
|
-
kind: 'hotel.focus',
|
|
568
|
-
hotelRef: 'hotel-1'
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
kind: 'hotel.selection',
|
|
572
|
-
hotelRef: 'hotel-1'
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
kind: 'offers.state',
|
|
576
|
-
hotelRefs: [
|
|
577
|
-
'hotel-1'
|
|
578
|
-
],
|
|
579
|
-
offerRefs: [
|
|
580
|
-
'offer-1'
|
|
581
|
-
],
|
|
582
|
-
loadedHotelCount: 1
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
kind: 'offer.selection',
|
|
586
|
-
offerRef: 'offer-1',
|
|
587
|
-
offerVersionRef: 'offer-1:v1'
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
kind: 'offer.availability',
|
|
591
|
-
offerRef: 'offer-1',
|
|
592
|
-
checkedOfferVersionRef: 'offer-1:v1',
|
|
593
|
-
currentOfferVersionRef: 'offer-1:v1',
|
|
594
|
-
available: true,
|
|
595
|
-
changedFactRefs: []
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
kind: 'checkout.handoff',
|
|
599
|
-
offerRef: 'offer-1',
|
|
600
|
-
offerVersionRef: 'offer-1:v1',
|
|
601
|
-
verifiedOfferRef: 'verified-1',
|
|
602
|
-
handoffRef: 'handoff-1'
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
kind: 'order.state',
|
|
606
|
-
orderRef: 'order-1',
|
|
607
|
-
state: 'pending'
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
kind: 'gap',
|
|
611
|
-
code: 'search_failed',
|
|
612
|
-
factRefs: [
|
|
613
|
-
'fact-1'
|
|
614
|
-
]
|
|
615
|
-
}
|
|
101
|
+
{ kind: 'search.state', searchSessionRef: 'session-1', resultCount: 1 }, { kind: 'results.state', matchedHotelRefs: ['hotel-1'], visibleCount: 1 },
|
|
102
|
+
{ kind: 'hotel.focus', hotelRef: 'hotel-1' }, { kind: 'hotel.selection', hotelRef: 'hotel-1' }, { kind: 'offers.state', hotelRefs: ['hotel-1'], offerRefs: ['offer-1'], loadedHotelCount: 1 },
|
|
103
|
+
{ kind: 'offer.selection', offerRef: 'offer-1', offerVersionRef: 'offer-1:v1' }, { kind: 'offer.availability', offerRef: 'offer-1', checkedOfferVersionRef: 'offer-1:v1', currentOfferVersionRef: 'offer-1:v1', available: true, changedFactRefs: [] },
|
|
104
|
+
{ kind: 'checkout.handoff', offerRef: 'offer-1', offerVersionRef: 'offer-1:v1', verifiedOfferRef: 'verified-1', handoffRef: 'handoff-1' }, { kind: 'order.state', orderRef: 'order-1', state: 'pending' }, { kind: 'gap', code: 'search_failed', factRefs: ['fact-1'] },
|
|
616
105
|
];
|
|
617
|
-
for (const observation of observations){
|
|
618
|
-
accept(`valid observation ${observation.kind}`, {
|
|
619
|
-
|
|
620
|
-
observation
|
|
621
|
-
});
|
|
622
|
-
reject(`observation extra ${observation.kind}`, {
|
|
623
|
-
...receipt,
|
|
624
|
-
observation: {
|
|
625
|
-
...observation,
|
|
626
|
-
extra: true
|
|
627
|
-
}
|
|
628
|
-
});
|
|
106
|
+
for (const observation of observations) {
|
|
107
|
+
accept(`valid observation ${observation.kind}`, { ...receipt, observation });
|
|
108
|
+
reject(`observation extra ${observation.kind}`, { ...receipt, observation: { ...observation, extra: true } });
|
|
629
109
|
}
|
|
630
|
-
for (const observation of observations.filter((item)=>[
|
|
631
|
-
|
|
632
|
-
'results.state',
|
|
633
|
-
'offers.state',
|
|
634
|
-
'offer.availability',
|
|
635
|
-
'order.state'
|
|
636
|
-
].includes(item.kind))){
|
|
637
|
-
reject(`observation dynamic gap ${observation.kind}`, {
|
|
638
|
-
...receipt,
|
|
639
|
-
observation: {
|
|
640
|
-
...observation,
|
|
641
|
-
gapCodes: [
|
|
642
|
-
'dynamic'
|
|
643
|
-
]
|
|
644
|
-
}
|
|
645
|
-
});
|
|
110
|
+
for (const observation of observations.filter((item) => ['search.state', 'results.state', 'offers.state', 'offer.availability', 'order.state'].includes(item.kind))) {
|
|
111
|
+
reject(`observation dynamic gap ${observation.kind}`, { ...receipt, observation: { ...observation, gapCodes: ['dynamic'] } });
|
|
646
112
|
}
|
|
647
|
-
reject('gap observation dynamic code', {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
code: 'dynamic',
|
|
652
|
-
factRefs: []
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
const turn = {
|
|
656
|
-
schemaVersion: 'booking.surface',
|
|
657
|
-
kind: 'user.turn',
|
|
658
|
-
taskId: 'task-1',
|
|
659
|
-
turnId: 'turn-1',
|
|
660
|
-
workspace,
|
|
661
|
-
request: {
|
|
662
|
-
text: 'find hotels'
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
const ingress = {
|
|
666
|
-
schemaVersion: 'booking.surface',
|
|
667
|
-
kind: 'user.turn.ingress',
|
|
668
|
-
requestKey: 'request-1',
|
|
669
|
-
taskHandle: 'opaque-task-handle-1',
|
|
670
|
-
surfaceHint: 'tenant',
|
|
671
|
-
workspace: ingressWorkspace,
|
|
672
|
-
request: {
|
|
673
|
-
text: 'find hotels'
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
const continuation = {
|
|
677
|
-
schemaVersion: 'booking.surface',
|
|
678
|
-
kind: 'action.receipt.continuation',
|
|
679
|
-
taskId: 'task-1',
|
|
680
|
-
workspace,
|
|
681
|
-
receipt
|
|
682
|
-
};
|
|
113
|
+
reject('gap observation dynamic code', { ...receipt, observation: { kind: 'gap', code: 'dynamic', factRefs: [] } });
|
|
114
|
+
const turn = { schemaVersion: 'booking.surface', kind: 'user.turn', taskId: 'task-1', turnId: 'turn-1', workspace, request: { text: 'find hotels' } };
|
|
115
|
+
const ingress = { schemaVersion: 'booking.surface', kind: 'user.turn.ingress', requestKey: 'request-1', taskHandle: 'opaque-task-handle-1', surfaceHint: 'tenant', workspace: ingressWorkspace, request: { text: 'find hotels' } };
|
|
116
|
+
const continuation = { schemaVersion: 'booking.surface', kind: 'action.receipt.continuation', taskId: 'task-1', workspace, receipt };
|
|
683
117
|
accept('valid user turn', turn);
|
|
684
118
|
accept('valid ingress', ingress);
|
|
685
|
-
accept('valid ingress approval carries executable typed approval', {
|
|
686
|
-
...ingress,
|
|
687
|
-
request: {
|
|
688
|
-
text: 'relax must criterion',
|
|
689
|
-
approval
|
|
690
|
-
}
|
|
691
|
-
});
|
|
119
|
+
accept('valid ingress approval carries executable typed approval', { ...ingress, request: { text: 'relax must criterion', approval } });
|
|
692
120
|
accept('valid continuation', continuation);
|
|
693
|
-
reject('empty text', {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
});
|
|
699
|
-
reject('
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
709
|
-
reject('
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
approval: [
|
|
714
|
-
approval,
|
|
715
|
-
approval
|
|
716
|
-
]
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
accept('text only remains non-approval', {
|
|
720
|
-
...turn,
|
|
721
|
-
request: {
|
|
722
|
-
text: 'please relax must'
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
const selectedIngressWorkspace = {
|
|
726
|
-
...ingressWorkspace,
|
|
727
|
-
visibleHotels: [
|
|
728
|
-
{
|
|
729
|
-
hotelRef: 'hotel-1',
|
|
730
|
-
name: 'Hotel 1',
|
|
731
|
-
factRefs: [
|
|
732
|
-
'hotel-fact-1'
|
|
733
|
-
]
|
|
734
|
-
}
|
|
735
|
-
],
|
|
736
|
-
loadedOffers: [
|
|
737
|
-
{
|
|
738
|
-
offerRef: 'offer-1',
|
|
739
|
-
offerVersionRef: 'offer-1:v1',
|
|
740
|
-
hotelRef: 'hotel-1',
|
|
741
|
-
evidenceLevel: 'rate_loaded',
|
|
742
|
-
factRefs: [
|
|
743
|
-
'offer-fact-1'
|
|
744
|
-
]
|
|
745
|
-
}
|
|
746
|
-
],
|
|
747
|
-
focusedHotelRef: 'hotel-1',
|
|
748
|
-
selectedOfferRef: 'offer-1'
|
|
749
|
-
};
|
|
750
|
-
accept('ingress preserves visible focus and loaded selection', {
|
|
751
|
-
...ingress,
|
|
752
|
-
workspace: selectedIngressWorkspace
|
|
753
|
-
});
|
|
754
|
-
reject('ingress rejects invisible focus', {
|
|
755
|
-
...ingress,
|
|
756
|
-
workspace: {
|
|
757
|
-
...selectedIngressWorkspace,
|
|
758
|
-
focusedHotelRef: 'hotel-other'
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
reject('ingress rejects unloaded selection', {
|
|
762
|
-
...ingress,
|
|
763
|
-
workspace: {
|
|
764
|
-
...selectedIngressWorkspace,
|
|
765
|
-
selectedOfferRef: 'offer-other'
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
reject('ingress rejects selection whose hotel is not visible', {
|
|
769
|
-
...ingress,
|
|
770
|
-
workspace: {
|
|
771
|
-
...selectedIngressWorkspace,
|
|
772
|
-
visibleHotels: [
|
|
773
|
-
{
|
|
774
|
-
hotelRef: 'hotel-other',
|
|
775
|
-
name: 'Other',
|
|
776
|
-
factRefs: []
|
|
777
|
-
}
|
|
778
|
-
]
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
reject('ingress never accepts verified authority', {
|
|
782
|
-
...ingress,
|
|
783
|
-
workspace: {
|
|
784
|
-
...selectedIngressWorkspace,
|
|
785
|
-
verifiedOfferRef: 'verified-offer-1'
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
reject('missing user turn identity', {
|
|
789
|
-
...turn,
|
|
790
|
-
turnId: undefined
|
|
791
|
-
});
|
|
792
|
-
reject('missing ingress request key', {
|
|
793
|
-
...ingress,
|
|
794
|
-
requestKey: undefined
|
|
795
|
-
});
|
|
796
|
-
reject('unsafe user turn identity', {
|
|
797
|
-
...turn,
|
|
798
|
-
turnId: 'Bearer token'
|
|
799
|
-
});
|
|
800
|
-
reject('unsafe ingress task handle', {
|
|
801
|
-
...ingress,
|
|
802
|
-
taskHandle: 'user@example.com'
|
|
803
|
-
});
|
|
804
|
-
reject('missing user task identity', {
|
|
805
|
-
...turn,
|
|
806
|
-
taskId: undefined
|
|
807
|
-
});
|
|
808
|
-
reject('browser task identity injection', {
|
|
809
|
-
...ingress,
|
|
810
|
-
taskId: 'browser-task'
|
|
811
|
-
});
|
|
812
|
-
reject('browser turn identity injection', {
|
|
813
|
-
...ingress,
|
|
814
|
-
turnId: 'browser-turn'
|
|
815
|
-
});
|
|
816
|
-
reject('browser context identity injection', {
|
|
817
|
-
...ingress,
|
|
818
|
-
contextRef: null
|
|
819
|
-
});
|
|
820
|
-
reject('browser verified authority injection', {
|
|
821
|
-
...ingress,
|
|
822
|
-
workspace: {
|
|
823
|
-
...ingressWorkspace,
|
|
824
|
-
verifiedOfferRef: 'verified-offer-1'
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
assert.equal(validateCriterionBlocker({
|
|
828
|
-
...blocker,
|
|
829
|
-
evidence: {
|
|
830
|
-
factRefs: [],
|
|
831
|
-
gapCodes: []
|
|
832
|
-
}
|
|
833
|
-
}).ok, false);
|
|
121
|
+
reject('empty text', { ...turn, request: { text: '' } });
|
|
122
|
+
reject('multiple approvals', { ...turn, request: { text: 'x', approval: [approval, approval] } });
|
|
123
|
+
reject('ingress multiple approvals', { ...ingress, request: { text: 'x', approval: [approval, approval] } });
|
|
124
|
+
accept('text only remains non-approval', { ...turn, request: { text: 'please relax must' } });
|
|
125
|
+
const selectedIngressWorkspace = { ...ingressWorkspace, visibleHotels: [{ hotelRef: 'hotel-1', name: 'Hotel 1', factRefs: ['hotel-fact-1'] }], loadedOffers: [{ offerRef: 'offer-1', offerVersionRef: 'offer-1:v1', hotelRef: 'hotel-1', evidenceLevel: 'rate_loaded', factRefs: ['offer-fact-1'] }], focusedHotelRef: 'hotel-1', selectedOfferRef: 'offer-1' };
|
|
126
|
+
accept('ingress preserves visible focus and loaded selection', { ...ingress, workspace: selectedIngressWorkspace });
|
|
127
|
+
reject('ingress rejects invisible focus', { ...ingress, workspace: { ...selectedIngressWorkspace, focusedHotelRef: 'hotel-other' } });
|
|
128
|
+
reject('ingress rejects unloaded selection', { ...ingress, workspace: { ...selectedIngressWorkspace, selectedOfferRef: 'offer-other' } });
|
|
129
|
+
reject('ingress rejects selection whose hotel is not visible', { ...ingress, workspace: { ...selectedIngressWorkspace, visibleHotels: [{ hotelRef: 'hotel-other', name: 'Other', factRefs: [] }] } });
|
|
130
|
+
reject('ingress never accepts verified authority', { ...ingress, workspace: { ...selectedIngressWorkspace, verifiedOfferRef: 'verified-offer-1' } });
|
|
131
|
+
reject('missing user turn identity', { ...turn, turnId: undefined });
|
|
132
|
+
reject('missing ingress request key', { ...ingress, requestKey: undefined });
|
|
133
|
+
reject('unsafe user turn identity', { ...turn, turnId: 'Bearer token' });
|
|
134
|
+
reject('unsafe ingress task handle', { ...ingress, taskHandle: 'user@example.com' });
|
|
135
|
+
reject('missing user task identity', { ...turn, taskId: undefined });
|
|
136
|
+
reject('browser task identity injection', { ...ingress, taskId: 'browser-task' });
|
|
137
|
+
reject('browser turn identity injection', { ...ingress, turnId: 'browser-turn' });
|
|
138
|
+
reject('browser context identity injection', { ...ingress, contextRef: null });
|
|
139
|
+
reject('browser verified authority injection', { ...ingress, workspace: { ...ingressWorkspace, verifiedOfferRef: 'verified-offer-1' } });
|
|
140
|
+
assert.equal(validateCriterionBlocker({ ...blocker, evidence: { factRefs: [], gapCodes: [] } }).ok, false);
|
|
834
141
|
negative++;
|
|
835
|
-
assert.equal(validateApprovalAgainstBlocker({
|
|
836
|
-
...approval,
|
|
837
|
-
blockerId: 'wrong'
|
|
838
|
-
}, blocker).ok, false);
|
|
142
|
+
assert.equal(validateApprovalAgainstBlocker({ ...approval, blockerId: 'wrong' }, blocker).ok, false);
|
|
839
143
|
negative++;
|
|
840
|
-
const continuationBlocked = {
|
|
841
|
-
...continuation,
|
|
842
|
-
receipt: {
|
|
843
|
-
...receipt,
|
|
844
|
-
resultContract: {
|
|
845
|
-
...receipt.resultContract,
|
|
846
|
-
hardCriteriaMet: true
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
};
|
|
144
|
+
const continuationBlocked = { ...continuation, receipt: { ...receipt, resultContract: { ...receipt.resultContract, hardCriteriaMet: true } } };
|
|
850
145
|
reject('continuation hard criteria with blocker', continuationBlocked);
|
|
851
|
-
reject('formatted phone', {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
});
|
|
857
|
-
reject('
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
});
|
|
863
|
-
reject('
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
});
|
|
869
|
-
reject('
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
request: {
|
|
884
|
-
text: '+971\u00a050\u00a0123\u00a04567'
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
reject('embedded phone', {
|
|
888
|
-
...turn,
|
|
889
|
-
request: {
|
|
890
|
-
text: 'call +971 50 123 4567 please'
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
reject('payment card', {
|
|
894
|
-
...turn,
|
|
895
|
-
request: {
|
|
896
|
-
text: '4111 1111 1111 1111'
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
reject('supplier cost', {
|
|
900
|
-
...turn,
|
|
901
|
-
request: {
|
|
902
|
-
text: 'supplier net cost AED 42'
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
reject('supplier cost colon', {
|
|
906
|
-
...turn,
|
|
907
|
-
request: {
|
|
908
|
-
text: 'supplier net cost: AED 42'
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
reject('supplier possessive cost', {
|
|
912
|
-
...turn,
|
|
913
|
-
request: {
|
|
914
|
-
text: "supplier's net cost AED 42"
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
reject('supplier cost reversed currency', {
|
|
918
|
-
...turn,
|
|
919
|
-
request: {
|
|
920
|
-
text: 'supplier cost 42 AED'
|
|
921
|
-
}
|
|
922
|
-
});
|
|
923
|
-
reject('supplier wholesale', {
|
|
924
|
-
...turn,
|
|
925
|
-
request: {
|
|
926
|
-
text: 'supplier wholesale rate AED 42'
|
|
927
|
-
}
|
|
928
|
-
});
|
|
929
|
-
reject('supplier base bypass', {
|
|
930
|
-
...turn,
|
|
931
|
-
request: {
|
|
932
|
-
text: 'supplier base cost AED 42'
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
reject('vendor cost', {
|
|
936
|
-
...turn,
|
|
937
|
-
request: {
|
|
938
|
-
text: 'vendor net cost AED 42'
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
reject('vendor compact amount', {
|
|
942
|
-
...turn,
|
|
943
|
-
request: {
|
|
944
|
-
text: 'vendor price AED42'
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
reject('internal supplier cost', {
|
|
948
|
-
...turn,
|
|
949
|
-
request: {
|
|
950
|
-
text: 'internal supplier cost AED 42'
|
|
951
|
-
}
|
|
952
|
-
});
|
|
953
|
-
reject('internal reversed amount', {
|
|
954
|
-
...turn,
|
|
955
|
-
request: {
|
|
956
|
-
text: 'internal rate 42 USD'
|
|
957
|
-
}
|
|
958
|
-
});
|
|
959
|
-
reject('contact email bypass', {
|
|
960
|
-
...turn,
|
|
961
|
-
request: {
|
|
962
|
-
text: 'contact user@hotelbyte.com'
|
|
963
|
-
}
|
|
964
|
-
});
|
|
965
|
-
reject('quoted email local part', {
|
|
966
|
-
...turn,
|
|
967
|
-
request: {
|
|
968
|
-
text: 'contact "user"@hotelbyte.com'
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
reject('unicode email local part', {
|
|
972
|
-
...turn,
|
|
973
|
-
request: {
|
|
974
|
-
text: '联系 用户@hotelbyte.com'
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
reject('technical-looking real mailbox', {
|
|
978
|
-
...turn,
|
|
979
|
-
request: {
|
|
980
|
-
text: 'version@company.com'
|
|
981
|
-
}
|
|
982
|
-
});
|
|
983
|
-
accept('benign trace identifier', {
|
|
984
|
-
...turn,
|
|
985
|
-
request: {
|
|
986
|
-
text: 'trace 0000000000000000'
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
accept('benign sk-ills sentence', {
|
|
990
|
-
...turn,
|
|
991
|
-
request: {
|
|
992
|
-
text: 'sk-ills are useful'
|
|
993
|
-
}
|
|
994
|
-
});
|
|
995
|
-
accept('benign version email', {
|
|
996
|
-
...turn,
|
|
997
|
-
request: {
|
|
998
|
-
text: 'version@release.example'
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
accept('benign schema identifier', {
|
|
1002
|
-
...turn,
|
|
1003
|
-
request: {
|
|
1004
|
-
text: 'schema@v2.example'
|
|
1005
|
-
}
|
|
1006
|
-
});
|
|
1007
|
-
accept('benign budget', {
|
|
1008
|
-
...turn,
|
|
1009
|
-
request: {
|
|
1010
|
-
text: 'budget AED 1000'
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
accept('benign total price', {
|
|
1014
|
-
...turn,
|
|
1015
|
-
request: {
|
|
1016
|
-
text: 'total price under AED 1000'
|
|
1017
|
-
}
|
|
1018
|
-
});
|
|
1019
|
-
const common = {
|
|
1020
|
-
schemaVersion: 'booking.surface',
|
|
1021
|
-
eventId: 'e-1',
|
|
1022
|
-
taskId: 'task-1',
|
|
1023
|
-
contextRef: 'ctx-1',
|
|
1024
|
-
sequence: 1,
|
|
1025
|
-
emittedAt: '2026-08-30T12:00:00.000Z'
|
|
1026
|
-
};
|
|
1027
|
-
const questionEvent = {
|
|
1028
|
-
...common,
|
|
1029
|
-
kind: 'question',
|
|
1030
|
-
question: {
|
|
1031
|
-
questionId: 'q-1',
|
|
1032
|
-
prompt: 'Relax?',
|
|
1033
|
-
missingFields: [],
|
|
1034
|
-
type: 'relaxation_approval_required',
|
|
1035
|
-
blocker,
|
|
1036
|
-
approvalOptions: [
|
|
1037
|
-
{
|
|
1038
|
-
approval
|
|
1039
|
-
}
|
|
1040
|
-
]
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
const events = [
|
|
1044
|
-
{
|
|
1045
|
-
...common,
|
|
1046
|
-
kind: 'status',
|
|
1047
|
-
status: 'working'
|
|
1048
|
-
},
|
|
1049
|
-
questionEvent,
|
|
1050
|
-
{
|
|
1051
|
-
...common,
|
|
1052
|
-
kind: 'operation',
|
|
1053
|
-
action: actions['search.run']
|
|
1054
|
-
},
|
|
1055
|
-
{
|
|
1056
|
-
...common,
|
|
1057
|
-
kind: 'explanation',
|
|
1058
|
-
explanation: {
|
|
1059
|
-
text: 'x',
|
|
1060
|
-
factRefs: []
|
|
1061
|
-
}
|
|
1062
|
-
},
|
|
1063
|
-
{
|
|
1064
|
-
...common,
|
|
1065
|
-
kind: 'terminal',
|
|
1066
|
-
terminal: {
|
|
1067
|
-
status: 'completed',
|
|
1068
|
-
summary: 'x',
|
|
1069
|
-
factRefs: []
|
|
1070
|
-
}
|
|
1071
|
-
},
|
|
1072
|
-
{
|
|
1073
|
-
...common,
|
|
1074
|
-
kind: 'error',
|
|
1075
|
-
error: {
|
|
1076
|
-
code: 'ERR',
|
|
1077
|
-
message: 'x',
|
|
1078
|
-
retryable: false
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
];
|
|
1082
|
-
assert.equal(validateBookingSurfaceEvent({
|
|
1083
|
-
...questionEvent,
|
|
1084
|
-
question: {
|
|
1085
|
-
...questionEvent.question,
|
|
1086
|
-
approvalOptions: [
|
|
1087
|
-
{
|
|
1088
|
-
approval: {
|
|
1089
|
-
...approval,
|
|
1090
|
-
blockerId: 'wrong'
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
]
|
|
1094
|
-
}
|
|
1095
|
-
}).ok, false);
|
|
146
|
+
reject('formatted phone', { ...turn, request: { text: '+971 (50) 123 4567' } });
|
|
147
|
+
reject('dotted phone', { ...turn, request: { text: '+971.50.123.4567' } });
|
|
148
|
+
reject('slash phone', { ...turn, request: { text: 'call +971/50/123/4567' } });
|
|
149
|
+
reject('comma phone', { ...turn, request: { text: 'call +971,50,123,4567' } });
|
|
150
|
+
reject('unicode dash phone', { ...turn, request: { text: 'call +971—50—123—4567' } });
|
|
151
|
+
reject('nbsp phone', { ...turn, request: { text: '+971\u00a050\u00a0123\u00a04567' } });
|
|
152
|
+
reject('embedded phone', { ...turn, request: { text: 'call +971 50 123 4567 please' } });
|
|
153
|
+
reject('payment card', { ...turn, request: { text: '4111 1111 1111 1111' } });
|
|
154
|
+
reject('supplier cost', { ...turn, request: { text: 'supplier net cost AED 42' } });
|
|
155
|
+
reject('supplier cost colon', { ...turn, request: { text: 'supplier net cost: AED 42' } });
|
|
156
|
+
reject('supplier possessive cost', { ...turn, request: { text: "supplier's net cost AED 42" } });
|
|
157
|
+
reject('supplier cost reversed currency', { ...turn, request: { text: 'supplier cost 42 AED' } });
|
|
158
|
+
reject('supplier wholesale', { ...turn, request: { text: 'supplier wholesale rate AED 42' } });
|
|
159
|
+
reject('supplier base bypass', { ...turn, request: { text: 'supplier base cost AED 42' } });
|
|
160
|
+
reject('vendor cost', { ...turn, request: { text: 'vendor net cost AED 42' } });
|
|
161
|
+
reject('vendor compact amount', { ...turn, request: { text: 'vendor price AED42' } });
|
|
162
|
+
reject('internal supplier cost', { ...turn, request: { text: 'internal supplier cost AED 42' } });
|
|
163
|
+
reject('internal reversed amount', { ...turn, request: { text: 'internal rate 42 USD' } });
|
|
164
|
+
reject('contact email bypass', { ...turn, request: { text: 'contact user@hotelbyte.com' } });
|
|
165
|
+
reject('quoted email local part', { ...turn, request: { text: 'contact "user"@hotelbyte.com' } });
|
|
166
|
+
reject('unicode email local part', { ...turn, request: { text: '联系 用户@hotelbyte.com' } });
|
|
167
|
+
reject('technical-looking real mailbox', { ...turn, request: { text: 'version@company.com' } });
|
|
168
|
+
accept('benign trace identifier', { ...turn, request: { text: 'trace 0000000000000000' } });
|
|
169
|
+
accept('benign sk-ills sentence', { ...turn, request: { text: 'sk-ills are useful' } });
|
|
170
|
+
accept('benign version email', { ...turn, request: { text: 'version@release.example' } });
|
|
171
|
+
accept('benign schema identifier', { ...turn, request: { text: 'schema@v2.example' } });
|
|
172
|
+
accept('benign budget', { ...turn, request: { text: 'budget AED 1000' } });
|
|
173
|
+
accept('benign total price', { ...turn, request: { text: 'total price under AED 1000' } });
|
|
174
|
+
const common = { schemaVersion: 'booking.surface', eventId: 'e-1', taskId: 'task-1', contextRef: 'ctx-1', sequence: 1, emittedAt: '2026-08-30T12:00:00.000Z' };
|
|
175
|
+
const questionEvent = { ...common, kind: 'question', question: { questionId: 'q-1', prompt: 'Relax?', missingFields: [], type: 'relaxation_approval_required', blocker, approvalOptions: [{ approval }] } };
|
|
176
|
+
const events = [{ ...common, kind: 'status', status: 'working' }, questionEvent, { ...common, kind: 'operation', action: actions['search.run'] }, { ...common, kind: 'explanation', explanation: { text: 'x', factRefs: [] } }, { ...common, kind: 'terminal', terminal: { status: 'completed', summary: 'x', factRefs: [] } }, { ...common, kind: 'error', error: { code: 'ERR', message: 'x', retryable: false } }];
|
|
177
|
+
assert.equal(validateBookingSurfaceEvent({ ...questionEvent, question: { ...questionEvent.question, approvalOptions: [{ approval: { ...approval, blockerId: 'wrong' } }] } }).ok, false);
|
|
1096
178
|
negative++;
|
|
1097
|
-
assert.equal(validateBookingSurfaceEvent({
|
|
1098
|
-
...questionEvent,
|
|
1099
|
-
question: {
|
|
1100
|
-
...questionEvent.question,
|
|
1101
|
-
approvalOptions: [
|
|
1102
|
-
{
|
|
1103
|
-
approval: {
|
|
1104
|
-
...approval,
|
|
1105
|
-
taskId: 'task-other'
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
]
|
|
1109
|
-
}
|
|
1110
|
-
}).ok, false);
|
|
179
|
+
assert.equal(validateBookingSurfaceEvent({ ...questionEvent, question: { ...questionEvent.question, approvalOptions: [{ approval: { ...approval, taskId: 'task-other' } }] } }).ok, false);
|
|
1111
180
|
negative++;
|
|
1112
|
-
assert.equal(validateBookingSurfaceEvent({
|
|
1113
|
-
...questionEvent,
|
|
1114
|
-
question: {
|
|
1115
|
-
...questionEvent.question,
|
|
1116
|
-
approvalOptions: [
|
|
1117
|
-
{
|
|
1118
|
-
approval: {
|
|
1119
|
-
...approval,
|
|
1120
|
-
contextRef: 'ctx-other'
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
]
|
|
1124
|
-
}
|
|
1125
|
-
}).ok, false);
|
|
181
|
+
assert.equal(validateBookingSurfaceEvent({ ...questionEvent, question: { ...questionEvent.question, approvalOptions: [{ approval: { ...approval, contextRef: 'ctx-other' } }] } }).ok, false);
|
|
1126
182
|
negative++;
|
|
1127
|
-
for (const event of events)
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
eventId: 'event-4242-4242-4242-4242'
|
|
1131
|
-
}).ok, true);
|
|
183
|
+
for (const event of events)
|
|
184
|
+
accept(`valid event ${event.kind}`, event);
|
|
185
|
+
assert.equal(validateBookingSurfaceEvent({ ...events[5], eventId: 'event-4242-4242-4242-4242' }).ok, true);
|
|
1132
186
|
positive++;
|
|
1133
|
-
assert.equal(validateBookingSurfaceEvent({
|
|
1134
|
-
...events[3],
|
|
1135
|
-
explanation: {
|
|
1136
|
-
text: 'card 4111 1111 1111 1111',
|
|
1137
|
-
factRefs: []
|
|
1138
|
-
}
|
|
1139
|
-
}).ok, false);
|
|
187
|
+
assert.equal(validateBookingSurfaceEvent({ ...events[3], explanation: { text: 'card 4111 1111 1111 1111', factRefs: [] } }).ok, false);
|
|
1140
188
|
negative++;
|
|
1141
|
-
reject('event mixed payload', {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
});
|
|
1145
|
-
reject('
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
});
|
|
1149
|
-
reject('
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
open: true
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
reject('request key whitespace', {
|
|
1159
|
-
...ingress,
|
|
1160
|
-
requestKey: 'request key'
|
|
1161
|
-
});
|
|
1162
|
-
reject('request key newline', {
|
|
1163
|
-
...ingress,
|
|
1164
|
-
requestKey: 'request-1\nnext'
|
|
1165
|
-
});
|
|
1166
|
-
reject('request key path', {
|
|
1167
|
-
...ingress,
|
|
1168
|
-
requestKey: 'request/path'
|
|
1169
|
-
});
|
|
1170
|
-
reject('request key email', {
|
|
1171
|
-
...ingress,
|
|
1172
|
-
requestKey: 'user@example.com'
|
|
1173
|
-
});
|
|
1174
|
-
reject('request key secret-like', {
|
|
1175
|
-
...ingress,
|
|
1176
|
-
requestKey: 'sk-live-abcdefghijkl'
|
|
1177
|
-
});
|
|
1178
|
-
reject('request key too short', {
|
|
1179
|
-
...ingress,
|
|
1180
|
-
requestKey: 'r1'
|
|
1181
|
-
});
|
|
1182
|
-
accept('ISO date and currency', {
|
|
1183
|
-
...workspace,
|
|
1184
|
-
searchDraft: {
|
|
1185
|
-
stay: {
|
|
1186
|
-
checkIn: '2026-09-01',
|
|
1187
|
-
checkOut: '2026-09-03'
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
});
|
|
1191
|
-
reject('email secret', {
|
|
1192
|
-
...turn,
|
|
1193
|
-
request: {
|
|
1194
|
-
text: 'user@example.com'
|
|
1195
|
-
}
|
|
1196
|
-
});
|
|
1197
|
-
reject('Bearer secret', {
|
|
1198
|
-
...turn,
|
|
1199
|
-
request: {
|
|
1200
|
-
text: 'Bearer abc'
|
|
1201
|
-
}
|
|
1202
|
-
});
|
|
1203
|
-
reject('sk secret', {
|
|
1204
|
-
...turn,
|
|
1205
|
-
request: {
|
|
1206
|
-
text: 'sk-live-abcdefghijkl'
|
|
1207
|
-
}
|
|
1208
|
-
});
|
|
1209
|
-
reject('JWT secret', {
|
|
1210
|
-
...turn,
|
|
1211
|
-
request: {
|
|
1212
|
-
text: 'eyJabc.def.ghi'
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
189
|
+
reject('event mixed payload', { ...events[0], question: {} });
|
|
190
|
+
reject('event missing payload', { ...events[1], question: undefined });
|
|
191
|
+
reject('event open nested', { ...events[4], terminal: { status: 'completed', summary: 'x', factRefs: [], open: true } });
|
|
192
|
+
reject('request key whitespace', { ...ingress, requestKey: 'request key' });
|
|
193
|
+
reject('request key newline', { ...ingress, requestKey: 'request-1\nnext' });
|
|
194
|
+
reject('request key path', { ...ingress, requestKey: 'request/path' });
|
|
195
|
+
reject('request key email', { ...ingress, requestKey: 'user@example.com' });
|
|
196
|
+
reject('request key secret-like', { ...ingress, requestKey: 'sk-live-abcdefghijkl' });
|
|
197
|
+
reject('request key too short', { ...ingress, requestKey: 'r1' });
|
|
198
|
+
accept('ISO date and currency', { ...workspace, searchDraft: { stay: { checkIn: '2026-09-01', checkOut: '2026-09-03' } } });
|
|
199
|
+
reject('email secret', { ...turn, request: { text: 'user@example.com' } });
|
|
200
|
+
reject('Bearer secret', { ...turn, request: { text: 'Bearer abc' } });
|
|
201
|
+
reject('sk secret', { ...turn, request: { text: 'sk-live-abcdefghijkl' } });
|
|
202
|
+
reject('JWT secret', { ...turn, request: { text: 'eyJabc.def.ghi' } });
|
|
1215
203
|
accept('E164-like ordinary date safe', turn);
|
|
1216
204
|
assert.equal(createHash('sha256').update(readFileSync(new URL('../../schemas/booking.surface.schema.json', import.meta.url))).digest('hex'), BOOKING_SURFACE_SCHEMA_SHA256);
|
|
1217
205
|
console.log(`BOOKING SURFACE CONTRACT PROOF: positive=${positive} negative=${negative}`);
|
|
1218
206
|
assert.ok(positive >= 65 && negative >= 105);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
//# sourceURL=ts/scripts/booking-surface-contract-proof-tests.ts
|