@danceiny/gotry 0.0.1-rc.16 → 0.0.1-rc.18
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 +161 -116
- package/README.zh-CN.md +173 -144
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +113 -66
- package/bin/gotry-inner.js +436 -70
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +50 -0
- package/bin/gotry.js +1 -1
- package/cordis.gotry-patch.yml +5 -1
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +1 -1
- package/dist/capabilities/effect.js +1 -1
- package/dist/capabilities/fact-log.js +1 -1
- package/dist/capabilities/flyai.js +20 -6
- package/dist/capabilities/hbcli.js +1 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/model-override.js +18 -0
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +1 -1
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +1 -1
- package/dist/capabilities/session/extension-bridge.js +19 -6
- package/dist/capabilities/session/extension-channel.js +3 -3
- package/dist/capabilities/session/extension-distribution.js +234 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/golden-score.js +92 -0
- package/dist/capabilities/session/health-watch.js +1 -1
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/static-flight-golden.js +137 -0
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +21 -251
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +11 -5
- package/dist/capabilities/session-search.js +46 -4
- package/dist/capabilities/weather.js +168 -46
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +102 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-planning-budget-e2e.js +227 -0
- package/dist/scripts/agent-planning-budget-tests.js +173 -0
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
- package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
- package/dist/scripts/booking-saga-tests.js +1 -1
- package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
- package/dist/scripts/bootstrap-tests.js +45 -39
- package/dist/scripts/build-changelog.js +1 -1
- package/dist/scripts/changelog-tests.js +1 -1
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
- package/dist/scripts/dsh-runtime-closure.js +150 -0
- package/dist/scripts/effect-tests.js +1 -1
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/evaluation-cadence-tests.js +347 -0
- package/dist/scripts/evaluation-contract-tests.js +574 -0
- package/dist/scripts/extension-distribution-cli.js +35 -0
- package/dist/scripts/extension-distribution-tests.js +341 -0
- package/dist/scripts/extension-tests.js +128 -23
- package/dist/scripts/fact-gate-tests.js +1 -1
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +1 -1
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/model-override-e2e.js +176 -0
- package/dist/scripts/nightly-evidence-tests.js +1 -1
- package/dist/scripts/nightly-evidence.js +1 -1
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +21 -53
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
- package/dist/scripts/price-drift-tests.js +1 -1
- package/dist/scripts/price-drift-watch.js +1 -1
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +40 -4
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +70 -20
- package/dist/scripts/sf-live-benchmark.js +338 -0
- package/dist/scripts/sf-live-cli-tests.js +21 -0
- package/dist/scripts/sf-soft-score-tests.js +108 -0
- package/dist/scripts/sf-summary.js +93 -0
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke-session-gate-tests.js +29 -0
- package/dist/scripts/smoke.js +79 -33
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/static-golden-tests.js +299 -0
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +694 -44
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +1 -1
- package/dist/src/benchmark-agent-conformance.js +370 -0
- package/dist/src/benchmark-environment-bridge.js +384 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
- package/dist/src/benchmark-tool-isolation.js +124 -0
- package/dist/src/bookable-facts.js +1 -1
- package/dist/src/booking-saga.js +1 -1
- package/dist/src/booking-surface/availability-policy-v2.js +830 -0
- package/dist/src/booking-surface/canonical-schema.js +113 -0
- package/dist/src/booking-surface/contracts-v2.js +89 -0
- package/dist/src/booking-surface/contracts.js +46 -0
- package/dist/src/booking-surface/dsh-planner.js +453 -0
- package/dist/src/booking-surface/dsh-plugin.js +93 -0
- package/dist/src/booking-surface/error-codes.js +94 -0
- package/dist/src/booking-surface/index.js +15 -0
- package/dist/src/booking-surface/profile.js +68 -0
- package/dist/src/booking-surface/runtime-v2.js +1771 -0
- package/dist/src/booking-surface/runtime.js +351 -0
- package/dist/src/booking-surface/server-v2.js +334 -0
- package/dist/src/booking-surface/server.js +302 -0
- package/dist/src/booking-surface/startup.js +159 -0
- package/dist/src/booking-surface/validation-v2.js +319 -0
- package/dist/src/booking-surface/validation.js +809 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +1 -1
- package/dist/src/engine.js +1 -1
- package/dist/src/evaluation-cadence.js +234 -0
- package/dist/src/evaluation-contracts.js +906 -0
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +50 -19
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +1 -1
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +2 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-budget.js +136 -0
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +31 -7
- package/package.json +286 -11
- package/schemas/booking.surface.v1.schema.json +927 -0
- package/schemas/booking.surface.v2.schema.json +61 -0
- package/ts/capabilities/flyai.ts +16 -3
- package/ts/capabilities/session/extension-bridge.ts +37 -14
- package/ts/capabilities/session/extension-channel.ts +6 -3
- package/ts/capabilities/session/extension-distribution.ts +264 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/wizard.ts +34 -176
- package/ts/capabilities/session-login.ts +15 -5
- package/ts/capabilities/session-search.ts +40 -3
- package/ts/capabilities/weather.ts +141 -52
- package/ts/package.json +3 -3
- package/ts/src/benchmark-agent-conformance.ts +448 -0
- package/ts/src/benchmark-environment-bridge.ts +348 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
- package/ts/src/booking-surface/canonical-schema.js +113 -0
- package/ts/src/booking-surface/contracts-v2.ts +118 -0
- package/ts/src/booking-surface/contracts.ts +380 -0
- package/ts/src/booking-surface/dsh-planner.ts +452 -0
- package/ts/src/booking-surface/dsh-plugin.js +93 -0
- package/ts/src/booking-surface/error-codes.ts +101 -0
- package/ts/src/booking-surface/index.ts +12 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/runtime-v2.ts +1466 -0
- package/ts/src/booking-surface/runtime.ts +483 -0
- package/ts/src/booking-surface/server-v2.ts +247 -0
- package/ts/src/booking-surface/server.ts +324 -0
- package/ts/src/booking-surface/startup.ts +196 -0
- package/ts/src/booking-surface/validation-v2.ts +205 -0
- package/ts/src/booking-surface/validation.ts +453 -0
- package/ts/src/index.ts +64 -11
- package/ts/src/state-ledger.ts +1 -0
- package/ts/src/tool-budget.ts +165 -0
- package/dist/scripts/wizard-bootstrap.js +0 -32
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import { BOOKING_READ_ACTION_KINDS, BOOKING_RECEIPT_STATUSES, BOOKING_SURFACES, BOOKING_SURFACE_EVENT_KINDS } from './contracts.js';
|
|
2
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
const CANONICAL_SCHEMA = JSON.parse(readFileSync(new URL('../../../schemas/booking.surface.v1.schema.json', import.meta.url), 'utf8'));
|
|
5
|
+
const CANONICAL_SCHEMA_ID = 'https://gotry.dev/schemas/booking.surface.v1.schema.json';
|
|
6
|
+
const canonicalAjv = new Ajv2020({
|
|
7
|
+
allErrors: true,
|
|
8
|
+
strict: true
|
|
9
|
+
});
|
|
10
|
+
canonicalAjv.addSchema(CANONICAL_SCHEMA);
|
|
11
|
+
const validateCanonicalAction = canonicalAjv.compile({
|
|
12
|
+
$ref: `${CANONICAL_SCHEMA_ID}#/$defs/BookingReadActionV1`
|
|
13
|
+
});
|
|
14
|
+
const validateCanonicalReceipt = canonicalAjv.compile({
|
|
15
|
+
$ref: `${CANONICAL_SCHEMA_ID}#/$defs/ActionReceiptV1`
|
|
16
|
+
});
|
|
17
|
+
const validateCanonicalWorkspace = canonicalAjv.compile({
|
|
18
|
+
$ref: `${CANONICAL_SCHEMA_ID}#/$defs/BookingWorkspaceSnapshotV1`
|
|
19
|
+
});
|
|
20
|
+
const validateCanonicalIngressTurn = canonicalAjv.compile({
|
|
21
|
+
$ref: `${CANONICAL_SCHEMA_ID}#/$defs/BookingCopilotIngressTurnV1`
|
|
22
|
+
});
|
|
23
|
+
const validateCanonicalTurn = canonicalAjv.compile({
|
|
24
|
+
$ref: `${CANONICAL_SCHEMA_ID}#/$defs/BookingCopilotTurnV1`
|
|
25
|
+
});
|
|
26
|
+
const ACTION_KINDS = new Set(BOOKING_READ_ACTION_KINDS);
|
|
27
|
+
const RECEIPT_STATUSES = new Set(BOOKING_RECEIPT_STATUSES);
|
|
28
|
+
const SURFACES = new Set(BOOKING_SURFACES);
|
|
29
|
+
const EVENT_KINDS = new Set(BOOKING_SURFACE_EVENT_KINDS);
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function isNonEmptyString(value) {
|
|
34
|
+
return typeof value === 'string' && value.length > 0;
|
|
35
|
+
}
|
|
36
|
+
function isRevision(value) {
|
|
37
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
38
|
+
}
|
|
39
|
+
function isStringArray(value) {
|
|
40
|
+
return Array.isArray(value) && value.every(isNonEmptyString);
|
|
41
|
+
}
|
|
42
|
+
function isCount(value) {
|
|
43
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
44
|
+
}
|
|
45
|
+
function isSafeCode(value) {
|
|
46
|
+
return typeof value === 'string' && /^[a-z][a-z0-9._-]*$/.test(value);
|
|
47
|
+
}
|
|
48
|
+
function receiptContainsUnsafeOpaqueText(value) {
|
|
49
|
+
if (typeof value === 'string') return /[^\s@]+@[^\s@]+\.[^\s@]+|Bearer\s+[^\s]+|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/i.test(value);
|
|
50
|
+
if (Array.isArray(value)) return value.some(receiptContainsUnsafeOpaqueText);
|
|
51
|
+
if (isRecord(value)) return Object.values(value).some(receiptContainsUnsafeOpaqueText);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function exactKeys(value, allowed, required, at, errors) {
|
|
55
|
+
const allow = new Set(allowed);
|
|
56
|
+
for (const key of Object.keys(value))if (!allow.has(key)) errors.push(`${at}.${key}: additional property is forbidden`);
|
|
57
|
+
for (const key of required)if (!Object.hasOwn(value, key)) errors.push(`${at}.${key}: required`);
|
|
58
|
+
}
|
|
59
|
+
function validateIngressWorkspace(value, at, errors) {
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
errors.push(`${at}: object required`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
exactKeys(value, [
|
|
65
|
+
'schemaVersion',
|
|
66
|
+
'revision',
|
|
67
|
+
'locale',
|
|
68
|
+
'currency',
|
|
69
|
+
'searchDraft',
|
|
70
|
+
'results',
|
|
71
|
+
'visibleHotels',
|
|
72
|
+
'loadedOffers',
|
|
73
|
+
'focusedHotelRef',
|
|
74
|
+
'shortlistedOfferRefs',
|
|
75
|
+
'selectedOfferRef',
|
|
76
|
+
'verifiedOfferRef'
|
|
77
|
+
], [
|
|
78
|
+
'schemaVersion',
|
|
79
|
+
'revision',
|
|
80
|
+
'locale',
|
|
81
|
+
'currency',
|
|
82
|
+
'searchDraft',
|
|
83
|
+
'results',
|
|
84
|
+
'visibleHotels',
|
|
85
|
+
'loadedOffers',
|
|
86
|
+
'shortlistedOfferRefs'
|
|
87
|
+
], at, errors);
|
|
88
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push(`${at}.schemaVersion: booking.surface.v1 required`);
|
|
89
|
+
if (!isRevision(value.revision)) errors.push(`${at}.revision: non-negative integer required`);
|
|
90
|
+
if (!isNonEmptyString(value.locale)) errors.push(`${at}.locale: non-empty string required`);
|
|
91
|
+
if (typeof value.currency !== 'string' || !/^[A-Z]{3}$/.test(value.currency)) errors.push(`${at}.currency: ISO-4217 code required`);
|
|
92
|
+
if (!isRecord(value.searchDraft)) errors.push(`${at}.searchDraft: object required`);
|
|
93
|
+
if (!isRecord(value.results)) errors.push(`${at}.results: object required`);
|
|
94
|
+
if (!Array.isArray(value.visibleHotels)) errors.push(`${at}.visibleHotels: array required`);
|
|
95
|
+
if (!Array.isArray(value.loadedOffers)) errors.push(`${at}.loadedOffers: array required`);
|
|
96
|
+
if (!isStringArray(value.shortlistedOfferRefs)) errors.push(`${at}.shortlistedOfferRefs: opaque-ref array required`);
|
|
97
|
+
return errors.length === 0;
|
|
98
|
+
}
|
|
99
|
+
function validateWorkspace(value, at, errors) {
|
|
100
|
+
if (!isRecord(value)) {
|
|
101
|
+
errors.push(`${at}: object required`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const { contextRef: _contextRef, surface: _surface, capabilities: _capabilities, ...ingressShape } = value;
|
|
105
|
+
validateIngressWorkspace(ingressShape, at, errors);
|
|
106
|
+
const allowed = [
|
|
107
|
+
'schemaVersion',
|
|
108
|
+
'contextRef',
|
|
109
|
+
'surface',
|
|
110
|
+
'revision',
|
|
111
|
+
'locale',
|
|
112
|
+
'currency',
|
|
113
|
+
'searchDraft',
|
|
114
|
+
'results',
|
|
115
|
+
'visibleHotels',
|
|
116
|
+
'loadedOffers',
|
|
117
|
+
'focusedHotelRef',
|
|
118
|
+
'shortlistedOfferRefs',
|
|
119
|
+
'selectedOfferRef',
|
|
120
|
+
'verifiedOfferRef',
|
|
121
|
+
'capabilities'
|
|
122
|
+
];
|
|
123
|
+
exactKeys(value, allowed, [
|
|
124
|
+
'schemaVersion',
|
|
125
|
+
'contextRef',
|
|
126
|
+
'surface',
|
|
127
|
+
'revision',
|
|
128
|
+
'locale',
|
|
129
|
+
'currency',
|
|
130
|
+
'searchDraft',
|
|
131
|
+
'results',
|
|
132
|
+
'visibleHotels',
|
|
133
|
+
'loadedOffers',
|
|
134
|
+
'shortlistedOfferRefs',
|
|
135
|
+
'capabilities'
|
|
136
|
+
], at, errors);
|
|
137
|
+
if (!isNonEmptyString(value.contextRef)) errors.push(`${at}.contextRef: server-minted opaque ref required`);
|
|
138
|
+
if (typeof value.surface !== 'string' || !SURFACES.has(value.surface)) errors.push(`${at}.surface: unsupported surface`);
|
|
139
|
+
if (!isRecord(value.capabilities)) {
|
|
140
|
+
errors.push(`${at}.capabilities: object required`);
|
|
141
|
+
} else {
|
|
142
|
+
exactKeys(value.capabilities, [
|
|
143
|
+
'surface',
|
|
144
|
+
'allowedActions'
|
|
145
|
+
], [
|
|
146
|
+
'surface',
|
|
147
|
+
'allowedActions'
|
|
148
|
+
], `${at}.capabilities`, errors);
|
|
149
|
+
if (value.capabilities.surface !== value.surface) errors.push(`${at}.capabilities.surface: must equal workspace.surface`);
|
|
150
|
+
const actionKinds = value.capabilities.allowedActions;
|
|
151
|
+
if (!Array.isArray(actionKinds) || !actionKinds.every((kind)=>typeof kind === 'string' && ACTION_KINDS.has(kind))) {
|
|
152
|
+
errors.push(`${at}.capabilities.allowedActions: closed action-kind array required`);
|
|
153
|
+
} else if (new Set(actionKinds).size !== actionKinds.length) {
|
|
154
|
+
errors.push(`${at}.capabilities.allowedActions: duplicates forbidden`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
appendCanonicalErrors(validateCanonicalWorkspace, value, at, errors);
|
|
158
|
+
return errors.length === 0;
|
|
159
|
+
}
|
|
160
|
+
const ACTION_INPUT_KEYS = {
|
|
161
|
+
'search.patch': {
|
|
162
|
+
allowed: [
|
|
163
|
+
'patch'
|
|
164
|
+
],
|
|
165
|
+
required: [
|
|
166
|
+
'patch'
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
'search.run': {
|
|
170
|
+
allowed: [],
|
|
171
|
+
required: []
|
|
172
|
+
},
|
|
173
|
+
'results.view.patch': {
|
|
174
|
+
allowed: [
|
|
175
|
+
'patch'
|
|
176
|
+
],
|
|
177
|
+
required: [
|
|
178
|
+
'patch'
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
'hotel.focus': {
|
|
182
|
+
allowed: [
|
|
183
|
+
'hotelRef'
|
|
184
|
+
],
|
|
185
|
+
required: [
|
|
186
|
+
'hotelRef'
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
'hotel.select': {
|
|
190
|
+
allowed: [
|
|
191
|
+
'hotelRef'
|
|
192
|
+
],
|
|
193
|
+
required: [
|
|
194
|
+
'hotelRef'
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
'offers.query': {
|
|
198
|
+
allowed: [
|
|
199
|
+
'hotelRefs',
|
|
200
|
+
'criteria'
|
|
201
|
+
],
|
|
202
|
+
required: [
|
|
203
|
+
'hotelRefs',
|
|
204
|
+
'criteria'
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
'offers.view.patch': {
|
|
208
|
+
allowed: [
|
|
209
|
+
'hotelRef',
|
|
210
|
+
'criteria'
|
|
211
|
+
],
|
|
212
|
+
required: [
|
|
213
|
+
'hotelRef',
|
|
214
|
+
'criteria'
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
'offers.compare': {
|
|
218
|
+
allowed: [
|
|
219
|
+
'offerRefs',
|
|
220
|
+
'requestedCount'
|
|
221
|
+
],
|
|
222
|
+
required: [
|
|
223
|
+
'offerRefs',
|
|
224
|
+
'requestedCount'
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
'offer.select': {
|
|
228
|
+
allowed: [
|
|
229
|
+
'offerRef'
|
|
230
|
+
],
|
|
231
|
+
required: [
|
|
232
|
+
'offerRef'
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
'offer.check': {
|
|
236
|
+
allowed: [
|
|
237
|
+
'offerRef'
|
|
238
|
+
],
|
|
239
|
+
required: [
|
|
240
|
+
'offerRef'
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
'checkout.prepare': {
|
|
244
|
+
allowed: [
|
|
245
|
+
'offerRef',
|
|
246
|
+
'verifiedOfferRef'
|
|
247
|
+
],
|
|
248
|
+
required: [
|
|
249
|
+
'offerRef',
|
|
250
|
+
'verifiedOfferRef'
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
'order.observe': {
|
|
254
|
+
allowed: [
|
|
255
|
+
'orderRef'
|
|
256
|
+
],
|
|
257
|
+
required: [
|
|
258
|
+
'orderRef'
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
function collectBookingReadActionErrors(value, at) {
|
|
263
|
+
const errors = [];
|
|
264
|
+
if (!isRecord(value)) return [
|
|
265
|
+
`${at}: typed action object required; text is never executable`
|
|
266
|
+
];
|
|
267
|
+
exactKeys(value, [
|
|
268
|
+
'schemaVersion',
|
|
269
|
+
'kind',
|
|
270
|
+
'actionId',
|
|
271
|
+
'contextRef',
|
|
272
|
+
'expectedRevision',
|
|
273
|
+
'reason',
|
|
274
|
+
'factRefs',
|
|
275
|
+
'input'
|
|
276
|
+
], [
|
|
277
|
+
'schemaVersion',
|
|
278
|
+
'kind',
|
|
279
|
+
'actionId',
|
|
280
|
+
'contextRef',
|
|
281
|
+
'expectedRevision',
|
|
282
|
+
'reason',
|
|
283
|
+
'factRefs',
|
|
284
|
+
'input'
|
|
285
|
+
], at, errors);
|
|
286
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push(`${at}.schemaVersion: booking.surface.v1 required`);
|
|
287
|
+
if (typeof value.kind !== 'string' || !ACTION_KINDS.has(value.kind)) {
|
|
288
|
+
errors.push(`${at}.kind: unregistered read action`);
|
|
289
|
+
}
|
|
290
|
+
if (!isNonEmptyString(value.actionId)) errors.push(`${at}.actionId: opaque ref required`);
|
|
291
|
+
if (!isNonEmptyString(value.contextRef)) errors.push(`${at}.contextRef: opaque ref required`);
|
|
292
|
+
if (!isRevision(value.expectedRevision)) errors.push(`${at}.expectedRevision: non-negative integer required`);
|
|
293
|
+
if (!isNonEmptyString(value.reason)) errors.push(`${at}.reason: non-empty explanation required`);
|
|
294
|
+
if (!isStringArray(value.factRefs)) errors.push(`${at}.factRefs: opaque-ref array required`);
|
|
295
|
+
if (!isRecord(value.input)) {
|
|
296
|
+
errors.push(`${at}.input: typed object required`);
|
|
297
|
+
} else if (typeof value.kind === 'string' && ACTION_KINDS.has(value.kind)) {
|
|
298
|
+
const spec = ACTION_INPUT_KEYS[value.kind];
|
|
299
|
+
exactKeys(value.input, spec.allowed, spec.required, `${at}.input`, errors);
|
|
300
|
+
}
|
|
301
|
+
if (!validateCanonicalAction(value)) {
|
|
302
|
+
for (const error of validateCanonicalAction.errors ?? []){
|
|
303
|
+
errors.push(canonicalError(error, at));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return errors;
|
|
307
|
+
}
|
|
308
|
+
function canonicalError(error, at) {
|
|
309
|
+
const path = error.instancePath ? error.instancePath.replaceAll('/', '.') : '';
|
|
310
|
+
const property = error.keyword === 'additionalProperties' ? `.${String(error.params.additionalProperty ?? '')}` : '';
|
|
311
|
+
return `${at}${path}${property}: ${error.message ?? error.keyword}`;
|
|
312
|
+
}
|
|
313
|
+
function appendCanonicalErrors(validate, value, at, errors) {
|
|
314
|
+
if (!validate(value)) for (const error of validate.errors ?? [])errors.push(canonicalError(error, at));
|
|
315
|
+
}
|
|
316
|
+
export function validateBookingReadActionV1(value) {
|
|
317
|
+
const errors = collectBookingReadActionErrors(value, '$');
|
|
318
|
+
return errors.length === 0 ? {
|
|
319
|
+
ok: true
|
|
320
|
+
} : {
|
|
321
|
+
ok: false,
|
|
322
|
+
errors
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function collectReceiptErrors(value, at) {
|
|
326
|
+
const errors = [];
|
|
327
|
+
if (!isRecord(value)) return [
|
|
328
|
+
`${at}: typed receipt object required`
|
|
329
|
+
];
|
|
330
|
+
exactKeys(value, [
|
|
331
|
+
'schemaVersion',
|
|
332
|
+
'kind',
|
|
333
|
+
'actionId',
|
|
334
|
+
'contextRef',
|
|
335
|
+
'status',
|
|
336
|
+
'revision',
|
|
337
|
+
'observation',
|
|
338
|
+
'resultContract',
|
|
339
|
+
'undoToken'
|
|
340
|
+
], [
|
|
341
|
+
'schemaVersion',
|
|
342
|
+
'kind',
|
|
343
|
+
'actionId',
|
|
344
|
+
'contextRef',
|
|
345
|
+
'status',
|
|
346
|
+
'revision',
|
|
347
|
+
'observation',
|
|
348
|
+
'resultContract'
|
|
349
|
+
], at, errors);
|
|
350
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push(`${at}.schemaVersion: booking.surface.v1 required`);
|
|
351
|
+
if (value.kind !== 'action.receipt') errors.push(`${at}.kind: action.receipt required`);
|
|
352
|
+
if (!isNonEmptyString(value.actionId)) errors.push(`${at}.actionId: opaque ref required`);
|
|
353
|
+
if (!isNonEmptyString(value.contextRef)) errors.push(`${at}.contextRef: opaque ref required`);
|
|
354
|
+
if (typeof value.status !== 'string' || !RECEIPT_STATUSES.has(value.status)) errors.push(`${at}.status: unregistered receipt status`);
|
|
355
|
+
if (!isRevision(value.revision)) errors.push(`${at}.revision: non-negative integer required`);
|
|
356
|
+
if (value.undoToken !== undefined && !isNonEmptyString(value.undoToken)) errors.push(`${at}.undoToken: opaque ref required`);
|
|
357
|
+
validateActionObservation(value.observation, `${at}.observation`, errors);
|
|
358
|
+
validateResultContract(value.resultContract, `${at}.resultContract`, errors);
|
|
359
|
+
return errors;
|
|
360
|
+
}
|
|
361
|
+
function validateActionObservation(value, at, errors) {
|
|
362
|
+
if (!isRecord(value)) {
|
|
363
|
+
errors.push(`${at}: discriminated object required`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
switch(value.kind){
|
|
367
|
+
case 'search.state':
|
|
368
|
+
exactKeys(value, [
|
|
369
|
+
'kind',
|
|
370
|
+
'searchSessionRef',
|
|
371
|
+
'resultCount'
|
|
372
|
+
], [
|
|
373
|
+
'kind'
|
|
374
|
+
], at, errors);
|
|
375
|
+
if (value.searchSessionRef !== undefined && !isNonEmptyString(value.searchSessionRef)) errors.push(`${at}.searchSessionRef: opaque ref required`);
|
|
376
|
+
if (value.resultCount !== undefined && !isCount(value.resultCount)) errors.push(`${at}.resultCount: non-negative integer required`);
|
|
377
|
+
return;
|
|
378
|
+
case 'results.state':
|
|
379
|
+
exactKeys(value, [
|
|
380
|
+
'kind',
|
|
381
|
+
'matchedHotelRefs',
|
|
382
|
+
'visibleCount'
|
|
383
|
+
], [
|
|
384
|
+
'kind',
|
|
385
|
+
'matchedHotelRefs',
|
|
386
|
+
'visibleCount'
|
|
387
|
+
], at, errors);
|
|
388
|
+
if (!isStringArray(value.matchedHotelRefs)) errors.push(`${at}.matchedHotelRefs: opaque-ref array required`);
|
|
389
|
+
if (!isCount(value.visibleCount)) errors.push(`${at}.visibleCount: non-negative integer required`);
|
|
390
|
+
return;
|
|
391
|
+
case 'hotel.focus':
|
|
392
|
+
exactKeys(value, [
|
|
393
|
+
'kind',
|
|
394
|
+
'hotelRef'
|
|
395
|
+
], [
|
|
396
|
+
'kind',
|
|
397
|
+
'hotelRef'
|
|
398
|
+
], at, errors);
|
|
399
|
+
if (!isNonEmptyString(value.hotelRef)) errors.push(`${at}.hotelRef: opaque ref required`);
|
|
400
|
+
return;
|
|
401
|
+
case 'hotel.selection':
|
|
402
|
+
exactKeys(value, [
|
|
403
|
+
'kind',
|
|
404
|
+
'hotelRef'
|
|
405
|
+
], [
|
|
406
|
+
'kind',
|
|
407
|
+
'hotelRef'
|
|
408
|
+
], at, errors);
|
|
409
|
+
if (!isNonEmptyString(value.hotelRef)) errors.push(`${at}.hotelRef: opaque ref required`);
|
|
410
|
+
return;
|
|
411
|
+
case 'offers.state':
|
|
412
|
+
exactKeys(value, [
|
|
413
|
+
'kind',
|
|
414
|
+
'hotelRefs',
|
|
415
|
+
'offerRefs',
|
|
416
|
+
'loadedHotelCount'
|
|
417
|
+
], [
|
|
418
|
+
'kind',
|
|
419
|
+
'hotelRefs',
|
|
420
|
+
'offerRefs',
|
|
421
|
+
'loadedHotelCount'
|
|
422
|
+
], at, errors);
|
|
423
|
+
if (!isStringArray(value.hotelRefs)) errors.push(`${at}.hotelRefs: opaque-ref array required`);
|
|
424
|
+
if (!isStringArray(value.offerRefs)) errors.push(`${at}.offerRefs: opaque-ref array required`);
|
|
425
|
+
if (!isCount(value.loadedHotelCount)) errors.push(`${at}.loadedHotelCount: non-negative integer required`);
|
|
426
|
+
return;
|
|
427
|
+
case 'offer.selection':
|
|
428
|
+
exactKeys(value, [
|
|
429
|
+
'kind',
|
|
430
|
+
'offerRef'
|
|
431
|
+
], [
|
|
432
|
+
'kind',
|
|
433
|
+
'offerRef'
|
|
434
|
+
], at, errors);
|
|
435
|
+
if (!isNonEmptyString(value.offerRef)) errors.push(`${at}.offerRef: opaque ref required`);
|
|
436
|
+
return;
|
|
437
|
+
case 'offer.availability':
|
|
438
|
+
exactKeys(value, [
|
|
439
|
+
'kind',
|
|
440
|
+
'offerRef',
|
|
441
|
+
'verifiedOfferRef',
|
|
442
|
+
'available',
|
|
443
|
+
'changedFactRefs'
|
|
444
|
+
], [
|
|
445
|
+
'kind',
|
|
446
|
+
'offerRef',
|
|
447
|
+
'available',
|
|
448
|
+
'changedFactRefs'
|
|
449
|
+
], at, errors);
|
|
450
|
+
if (!isNonEmptyString(value.offerRef)) errors.push(`${at}.offerRef: opaque ref required`);
|
|
451
|
+
if (value.verifiedOfferRef !== undefined && !isNonEmptyString(value.verifiedOfferRef)) errors.push(`${at}.verifiedOfferRef: opaque ref required`);
|
|
452
|
+
if (typeof value.available !== 'boolean') errors.push(`${at}.available: boolean required`);
|
|
453
|
+
if (!isStringArray(value.changedFactRefs)) errors.push(`${at}.changedFactRefs: opaque-ref array required`);
|
|
454
|
+
return;
|
|
455
|
+
case 'checkout.handoff':
|
|
456
|
+
exactKeys(value, [
|
|
457
|
+
'kind',
|
|
458
|
+
'offerRef',
|
|
459
|
+
'verifiedOfferRef',
|
|
460
|
+
'handoffRef'
|
|
461
|
+
], [
|
|
462
|
+
'kind',
|
|
463
|
+
'offerRef',
|
|
464
|
+
'verifiedOfferRef',
|
|
465
|
+
'handoffRef'
|
|
466
|
+
], at, errors);
|
|
467
|
+
if (!isNonEmptyString(value.offerRef)) errors.push(`${at}.offerRef: opaque ref required`);
|
|
468
|
+
if (!isNonEmptyString(value.verifiedOfferRef)) errors.push(`${at}.verifiedOfferRef: opaque ref required`);
|
|
469
|
+
if (!isNonEmptyString(value.handoffRef)) errors.push(`${at}.handoffRef: opaque ref required`);
|
|
470
|
+
return;
|
|
471
|
+
case 'order.state':
|
|
472
|
+
{
|
|
473
|
+
exactKeys(value, [
|
|
474
|
+
'kind',
|
|
475
|
+
'orderRef',
|
|
476
|
+
'state'
|
|
477
|
+
], [
|
|
478
|
+
'kind',
|
|
479
|
+
'orderRef',
|
|
480
|
+
'state'
|
|
481
|
+
], at, errors);
|
|
482
|
+
const states = new Set([
|
|
483
|
+
'pending',
|
|
484
|
+
'verified',
|
|
485
|
+
'failed',
|
|
486
|
+
'unknown'
|
|
487
|
+
]);
|
|
488
|
+
if (!isNonEmptyString(value.orderRef)) errors.push(`${at}.orderRef: opaque ref required`);
|
|
489
|
+
if (typeof value.state !== 'string' || !states.has(value.state)) errors.push(`${at}.state: unregistered order state`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
case 'gap':
|
|
493
|
+
exactKeys(value, [
|
|
494
|
+
'kind',
|
|
495
|
+
'code',
|
|
496
|
+
'factRefs'
|
|
497
|
+
], [
|
|
498
|
+
'kind',
|
|
499
|
+
'code',
|
|
500
|
+
'factRefs'
|
|
501
|
+
], at, errors);
|
|
502
|
+
if (!isSafeCode(value.code)) errors.push(`${at}.code: safe identifier required`);
|
|
503
|
+
if (!isStringArray(value.factRefs)) errors.push(`${at}.factRefs: opaque-ref array required`);
|
|
504
|
+
return;
|
|
505
|
+
default:
|
|
506
|
+
errors.push(`${at}.kind: unregistered observation kind`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function validateResultContract(value, at, errors) {
|
|
510
|
+
if (!isRecord(value)) {
|
|
511
|
+
errors.push(`${at}: object required`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
exactKeys(value, [
|
|
515
|
+
'outcome',
|
|
516
|
+
'requestedCount',
|
|
517
|
+
'actualCount',
|
|
518
|
+
'hardCriteriaMet',
|
|
519
|
+
'factRefs',
|
|
520
|
+
'gapCodes'
|
|
521
|
+
], [
|
|
522
|
+
'outcome',
|
|
523
|
+
'hardCriteriaMet',
|
|
524
|
+
'factRefs',
|
|
525
|
+
'gapCodes'
|
|
526
|
+
], at, errors);
|
|
527
|
+
const outcomes = new Set([
|
|
528
|
+
'complete',
|
|
529
|
+
'partial',
|
|
530
|
+
'empty'
|
|
531
|
+
]);
|
|
532
|
+
if (typeof value.outcome !== 'string' || !outcomes.has(value.outcome)) errors.push(`${at}.outcome: complete, partial, or empty required`);
|
|
533
|
+
if (value.requestedCount !== undefined && !isCount(value.requestedCount)) errors.push(`${at}.requestedCount: non-negative integer required`);
|
|
534
|
+
if (value.actualCount !== undefined && !isCount(value.actualCount)) errors.push(`${at}.actualCount: non-negative integer required`);
|
|
535
|
+
if (typeof value.hardCriteriaMet !== 'boolean') errors.push(`${at}.hardCriteriaMet: boolean required`);
|
|
536
|
+
if (!isStringArray(value.factRefs)) errors.push(`${at}.factRefs: opaque-ref array required`);
|
|
537
|
+
if (!Array.isArray(value.gapCodes) || !value.gapCodes.every(isSafeCode)) errors.push(`${at}.gapCodes: safe identifier array required`);
|
|
538
|
+
}
|
|
539
|
+
export function validateActionReceiptV1(value) {
|
|
540
|
+
const errors = collectReceiptErrors(value, '$');
|
|
541
|
+
if (receiptContainsUnsafeOpaqueText(value)) errors.push('$: receipt contains unsafe non-opaque text');
|
|
542
|
+
if (!validateCanonicalReceipt(value)) {
|
|
543
|
+
for (const error of validateCanonicalReceipt.errors ?? [])errors.push(canonicalError(error, '$'));
|
|
544
|
+
}
|
|
545
|
+
return errors.length === 0 ? {
|
|
546
|
+
ok: true
|
|
547
|
+
} : {
|
|
548
|
+
ok: false,
|
|
549
|
+
errors
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
export function validateBookingCopilotIngressTurnV1(value) {
|
|
553
|
+
const errors = [];
|
|
554
|
+
if (!isRecord(value)) return {
|
|
555
|
+
ok: false,
|
|
556
|
+
errors: [
|
|
557
|
+
'$: object required'
|
|
558
|
+
]
|
|
559
|
+
};
|
|
560
|
+
exactKeys(value, [
|
|
561
|
+
'schemaVersion',
|
|
562
|
+
'kind',
|
|
563
|
+
'taskId',
|
|
564
|
+
'contextRef',
|
|
565
|
+
'surfaceHint',
|
|
566
|
+
'workspace',
|
|
567
|
+
'request'
|
|
568
|
+
], [
|
|
569
|
+
'schemaVersion',
|
|
570
|
+
'kind',
|
|
571
|
+
'surfaceHint',
|
|
572
|
+
'workspace',
|
|
573
|
+
'request'
|
|
574
|
+
], '$', errors);
|
|
575
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push('$.schemaVersion: booking.surface.v1 required');
|
|
576
|
+
if (value.kind !== 'user.turn.ingress') errors.push('$.kind: user.turn.ingress required');
|
|
577
|
+
if (value.contextRef !== undefined && value.contextRef !== null) errors.push('$.contextRef: bootstrap may only omit it or send null');
|
|
578
|
+
if (typeof value.surfaceHint !== 'string' || !SURFACES.has(value.surfaceHint)) errors.push('$.surfaceHint: unsupported surface hint');
|
|
579
|
+
validateIngressWorkspace(value.workspace, '$.workspace', errors);
|
|
580
|
+
if (!isRecord(value.request)) {
|
|
581
|
+
errors.push('$.request: object required');
|
|
582
|
+
} else {
|
|
583
|
+
exactKeys(value.request, [
|
|
584
|
+
'text'
|
|
585
|
+
], [
|
|
586
|
+
'text'
|
|
587
|
+
], '$.request', errors);
|
|
588
|
+
if (!isNonEmptyString(value.request.text)) errors.push('$.request.text: non-empty string required');
|
|
589
|
+
}
|
|
590
|
+
appendCanonicalErrors(validateCanonicalIngressTurn, value, '$', errors);
|
|
591
|
+
return errors.length === 0 ? {
|
|
592
|
+
ok: true
|
|
593
|
+
} : {
|
|
594
|
+
ok: false,
|
|
595
|
+
errors
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
export function validateBookingCopilotTurnV1(value) {
|
|
599
|
+
const errors = [];
|
|
600
|
+
if (!isRecord(value)) return {
|
|
601
|
+
ok: false,
|
|
602
|
+
errors: [
|
|
603
|
+
'$: object required'
|
|
604
|
+
]
|
|
605
|
+
};
|
|
606
|
+
if (value.kind === 'user.turn') {
|
|
607
|
+
exactKeys(value, [
|
|
608
|
+
'schemaVersion',
|
|
609
|
+
'kind',
|
|
610
|
+
'taskId',
|
|
611
|
+
'workspace',
|
|
612
|
+
'request'
|
|
613
|
+
], [
|
|
614
|
+
'schemaVersion',
|
|
615
|
+
'kind',
|
|
616
|
+
'workspace',
|
|
617
|
+
'request'
|
|
618
|
+
], '$', errors);
|
|
619
|
+
if (!isRecord(value.request)) errors.push('$.request: object required');
|
|
620
|
+
else {
|
|
621
|
+
exactKeys(value.request, [
|
|
622
|
+
'text'
|
|
623
|
+
], [
|
|
624
|
+
'text'
|
|
625
|
+
], '$.request', errors);
|
|
626
|
+
if (!isNonEmptyString(value.request.text)) errors.push('$.request.text: non-empty string required');
|
|
627
|
+
}
|
|
628
|
+
} else if (value.kind === 'action.receipt.continuation') {
|
|
629
|
+
exactKeys(value, [
|
|
630
|
+
'schemaVersion',
|
|
631
|
+
'kind',
|
|
632
|
+
'taskId',
|
|
633
|
+
'workspace',
|
|
634
|
+
'receipt'
|
|
635
|
+
], [
|
|
636
|
+
'schemaVersion',
|
|
637
|
+
'kind',
|
|
638
|
+
'taskId',
|
|
639
|
+
'workspace',
|
|
640
|
+
'receipt'
|
|
641
|
+
], '$', errors);
|
|
642
|
+
if (!isNonEmptyString(value.taskId)) errors.push('$.taskId: opaque ref required');
|
|
643
|
+
errors.push(...collectReceiptErrors(value.receipt, '$.receipt'));
|
|
644
|
+
const receiptValidation = validateActionReceiptV1(value.receipt);
|
|
645
|
+
if (!receiptValidation.ok) errors.push(...receiptValidation.errors.map((error)=>`$.receipt${error.slice(1)}`));
|
|
646
|
+
} else {
|
|
647
|
+
errors.push('$.kind: user.turn or action.receipt.continuation required');
|
|
648
|
+
}
|
|
649
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push('$.schemaVersion: booking.surface.v1 required');
|
|
650
|
+
validateWorkspace(value.workspace, '$.workspace', errors);
|
|
651
|
+
if (value.kind === 'action.receipt.continuation' && isRecord(value.workspace) && isRecord(value.receipt)) {
|
|
652
|
+
if (value.receipt.contextRef !== value.workspace.contextRef) errors.push('$.receipt.contextRef: must equal workspace.contextRef');
|
|
653
|
+
if (value.receipt.revision !== value.workspace.revision) errors.push('$.receipt.revision: must equal workspace.revision');
|
|
654
|
+
}
|
|
655
|
+
appendCanonicalErrors(validateCanonicalTurn, value, '$', errors);
|
|
656
|
+
return errors.length === 0 ? {
|
|
657
|
+
ok: true
|
|
658
|
+
} : {
|
|
659
|
+
ok: false,
|
|
660
|
+
errors
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
export function validateBookingSurfaceEventV1(value) {
|
|
664
|
+
const errors = [];
|
|
665
|
+
if (!isRecord(value)) return {
|
|
666
|
+
ok: false,
|
|
667
|
+
errors: [
|
|
668
|
+
'$: typed event object required'
|
|
669
|
+
]
|
|
670
|
+
};
|
|
671
|
+
const common = [
|
|
672
|
+
'schemaVersion',
|
|
673
|
+
'eventId',
|
|
674
|
+
'taskId',
|
|
675
|
+
'contextRef',
|
|
676
|
+
'sequence',
|
|
677
|
+
'emittedAt',
|
|
678
|
+
'kind'
|
|
679
|
+
];
|
|
680
|
+
const branchKeys = {
|
|
681
|
+
status: 'status',
|
|
682
|
+
question: 'question',
|
|
683
|
+
operation: 'action',
|
|
684
|
+
explanation: 'explanation',
|
|
685
|
+
terminal: 'terminal',
|
|
686
|
+
error: 'error'
|
|
687
|
+
};
|
|
688
|
+
const branchKey = typeof value.kind === 'string' ? branchKeys[value.kind] : undefined;
|
|
689
|
+
exactKeys(value, [
|
|
690
|
+
...common,
|
|
691
|
+
...branchKey ? [
|
|
692
|
+
branchKey
|
|
693
|
+
] : []
|
|
694
|
+
], [
|
|
695
|
+
...common,
|
|
696
|
+
...branchKey ? [
|
|
697
|
+
branchKey
|
|
698
|
+
] : []
|
|
699
|
+
], '$', errors);
|
|
700
|
+
if (value.schemaVersion !== 'booking.surface.v1') errors.push('$.schemaVersion: booking.surface.v1 required');
|
|
701
|
+
if (!isNonEmptyString(value.eventId)) errors.push('$.eventId: opaque ref required');
|
|
702
|
+
if (!isNonEmptyString(value.taskId)) errors.push('$.taskId: opaque ref required');
|
|
703
|
+
if (!isNonEmptyString(value.contextRef)) errors.push('$.contextRef: server-minted opaque ref required');
|
|
704
|
+
if (!Number.isInteger(value.sequence) || Number(value.sequence) < 1) errors.push('$.sequence: positive integer required');
|
|
705
|
+
if (!isNonEmptyString(value.emittedAt)) errors.push('$.emittedAt: timestamp required');
|
|
706
|
+
if (typeof value.kind !== 'string' || !EVENT_KINDS.has(value.kind)) errors.push('$.kind: unregistered event kind');
|
|
707
|
+
if (value.kind === 'status') {
|
|
708
|
+
const statuses = new Set([
|
|
709
|
+
'submitted',
|
|
710
|
+
'working',
|
|
711
|
+
'waiting_receipt',
|
|
712
|
+
'input_required'
|
|
713
|
+
]);
|
|
714
|
+
if (typeof value.status !== 'string' || !statuses.has(value.status)) errors.push('$.status: unregistered task status');
|
|
715
|
+
}
|
|
716
|
+
if (value.kind === 'question') {
|
|
717
|
+
if (!isRecord(value.question)) {
|
|
718
|
+
errors.push('$.question: typed object required');
|
|
719
|
+
} else {
|
|
720
|
+
exactKeys(value.question, [
|
|
721
|
+
'questionId',
|
|
722
|
+
'prompt',
|
|
723
|
+
'missingFields'
|
|
724
|
+
], [
|
|
725
|
+
'questionId',
|
|
726
|
+
'prompt',
|
|
727
|
+
'missingFields'
|
|
728
|
+
], '$.question', errors);
|
|
729
|
+
if (!isNonEmptyString(value.question.questionId)) errors.push('$.question.questionId: opaque ref required');
|
|
730
|
+
if (!isNonEmptyString(value.question.prompt)) errors.push('$.question.prompt: non-empty string required');
|
|
731
|
+
if (!isStringArray(value.question.missingFields)) errors.push('$.question.missingFields: string array required');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (value.kind === 'operation') errors.push(...collectBookingReadActionErrors(value.action, '$.action'));
|
|
735
|
+
if (value.kind === 'explanation') {
|
|
736
|
+
if (!isRecord(value.explanation)) {
|
|
737
|
+
errors.push('$.explanation: typed object required');
|
|
738
|
+
} else {
|
|
739
|
+
exactKeys(value.explanation, [
|
|
740
|
+
'text',
|
|
741
|
+
'factRefs'
|
|
742
|
+
], [
|
|
743
|
+
'text',
|
|
744
|
+
'factRefs'
|
|
745
|
+
], '$.explanation', errors);
|
|
746
|
+
if (!isNonEmptyString(value.explanation.text)) errors.push('$.explanation.text: non-empty string required');
|
|
747
|
+
if (!isStringArray(value.explanation.factRefs)) errors.push('$.explanation.factRefs: opaque-ref array required');
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (value.kind === 'terminal') {
|
|
751
|
+
if (!isRecord(value.terminal)) {
|
|
752
|
+
errors.push('$.terminal: typed object required');
|
|
753
|
+
} else {
|
|
754
|
+
exactKeys(value.terminal, [
|
|
755
|
+
'status',
|
|
756
|
+
'summary',
|
|
757
|
+
'factRefs'
|
|
758
|
+
], [
|
|
759
|
+
'status',
|
|
760
|
+
'summary',
|
|
761
|
+
'factRefs'
|
|
762
|
+
], '$.terminal', errors);
|
|
763
|
+
const statuses = new Set([
|
|
764
|
+
'completed',
|
|
765
|
+
'stopped'
|
|
766
|
+
]);
|
|
767
|
+
if (typeof value.terminal.status !== 'string' || !statuses.has(value.terminal.status)) errors.push('$.terminal.status: completed or stopped required');
|
|
768
|
+
if (!isNonEmptyString(value.terminal.summary)) errors.push('$.terminal.summary: non-empty string required');
|
|
769
|
+
if (!isStringArray(value.terminal.factRefs)) errors.push('$.terminal.factRefs: opaque-ref array required');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (value.kind === 'error') {
|
|
773
|
+
if (!isRecord(value.error)) {
|
|
774
|
+
errors.push('$.error: typed object required');
|
|
775
|
+
} else {
|
|
776
|
+
exactKeys(value.error, [
|
|
777
|
+
'code',
|
|
778
|
+
'message',
|
|
779
|
+
'retryable'
|
|
780
|
+
], [
|
|
781
|
+
'code',
|
|
782
|
+
'message',
|
|
783
|
+
'retryable'
|
|
784
|
+
], '$.error', errors);
|
|
785
|
+
if (!isNonEmptyString(value.error.code)) errors.push('$.error.code: non-empty string required');
|
|
786
|
+
if (!isNonEmptyString(value.error.message)) errors.push('$.error.message: non-empty string required');
|
|
787
|
+
if (typeof value.error.retryable !== 'boolean') errors.push('$.error.retryable: boolean required');
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return errors.length === 0 ? {
|
|
791
|
+
ok: true
|
|
792
|
+
} : {
|
|
793
|
+
ok: false,
|
|
794
|
+
errors
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
export function bookingOperationFromEvent(value) {
|
|
798
|
+
if (!isRecord(value) || value.kind !== 'operation') return null;
|
|
799
|
+
if (!validateBookingSurfaceEventV1(value).ok) return null;
|
|
800
|
+
return value.action;
|
|
801
|
+
}
|
|
802
|
+
void null;
|
|
803
|
+
void null;
|
|
804
|
+
void null;
|
|
805
|
+
void null;
|
|
806
|
+
void null;
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
//# sourceURL=ts/src/booking-surface/validation.ts
|