@sequenceholdings/agent-spec 0.1.1 → 0.1.3
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 +93 -0
- package/dist/agent-definition.d.ts +616 -0
- package/dist/agent-definition.js +240 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +13 -0
- package/dist/index.d.ts +2 -117
- package/dist/index.js +2 -248
- package/dist/input-schema.d.ts +19 -0
- package/dist/input-schema.js +57 -0
- package/dist/voice-access.d.ts +40 -0
- package/dist/voice-access.js +34 -0
- package/dist/voice-actions-contracts.d.ts +423 -0
- package/dist/voice-actions-contracts.js +95 -0
- package/dist/voice-actions-transitions.d.ts +52 -0
- package/dist/voice-actions-transitions.js +170 -0
- package/dist/voice-actions.d.ts +2 -0
- package/dist/voice-actions.js +2 -0
- package/dist/voice-api.d.ts +1102 -0
- package/dist/voice-api.js +164 -0
- package/dist/voice-authoring.d.ts +463 -0
- package/dist/voice-authoring.js +132 -0
- package/dist/voice-authority-contracts.d.ts +247 -0
- package/dist/voice-authority-contracts.js +177 -0
- package/dist/voice-authority-evaluation.d.ts +29 -0
- package/dist/voice-authority-evaluation.js +80 -0
- package/dist/voice-authority-verification.d.ts +47 -0
- package/dist/voice-authority-verification.js +184 -0
- package/dist/voice-authority.d.ts +3 -0
- package/dist/voice-authority.js +3 -0
- package/dist/voice-common.d.ts +21 -0
- package/dist/voice-common.js +23 -0
- package/dist/voice-drafts.d.ts +441 -0
- package/dist/voice-drafts.js +75 -0
- package/dist/voice-grant.d.ts +19 -0
- package/dist/voice-grant.js +21 -0
- package/dist/voice-lifecycle-control.d.ts +130 -0
- package/dist/voice-lifecycle-control.js +138 -0
- package/dist/voice-lifecycle-session.d.ts +168 -0
- package/dist/voice-lifecycle-session.js +114 -0
- package/dist/voice-lifecycle-states.d.ts +96 -0
- package/dist/voice-lifecycle-states.js +139 -0
- package/dist/voice-lifecycle.d.ts +3 -0
- package/dist/voice-lifecycle.js +3 -0
- package/dist/voice-policy-duration.d.ts +46 -0
- package/dist/voice-policy-duration.js +166 -0
- package/dist/voice-policy-limits.d.ts +174 -0
- package/dist/voice-policy-limits.js +188 -0
- package/dist/voice-policy.d.ts +2 -0
- package/dist/voice-policy.js +2 -0
- package/dist/voice-provider-evidence.d.ts +80 -0
- package/dist/voice-provider-evidence.js +115 -0
- package/dist/voice-provider-publication.d.ts +319 -0
- package/dist/voice-provider-publication.js +117 -0
- package/dist/voice-provider-release.d.ts +386 -0
- package/dist/voice-provider-release.js +154 -0
- package/dist/voice-provider.d.ts +3 -0
- package/dist/voice-provider.js +3 -0
- package/dist/voice-release.d.ts +808 -0
- package/dist/voice-release.js +110 -0
- package/dist/voice-runs-audio.d.ts +157 -0
- package/dist/voice-runs-audio.js +85 -0
- package/dist/voice-runs-evidence.d.ts +183 -0
- package/dist/voice-runs-evidence.js +154 -0
- package/dist/voice-runs-model.d.ts +967 -0
- package/dist/voice-runs-model.js +199 -0
- package/dist/voice-runs-tooling.d.ts +88 -0
- package/dist/voice-runs-tooling.js +100 -0
- package/dist/voice-runs.d.ts +4 -0
- package/dist/voice-runs.js +4 -0
- package/dist/voice.d.ts +13 -0
- package/dist/voice.js +13 -0
- package/package.json +8 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { voiceAgentScopeSchema, voiceIdSchema, voiceRevisionSchema, voiceScopeSchema, voiceTimestampSchema, } from './voice-common.js';
|
|
3
|
+
import { voiceDependencyMaintenanceStateSchema } from './voice-lifecycle-states.js';
|
|
4
|
+
export const voicePublicationPauseSchema = voiceAgentScopeSchema.extend({
|
|
5
|
+
publicationOperationId: voiceIdSchema,
|
|
6
|
+
status: z.enum(['ACTIVE', 'RELEASED']),
|
|
7
|
+
revision: voiceRevisionSchema,
|
|
8
|
+
claimedAt: voiceTimestampSchema,
|
|
9
|
+
releasedAt: voiceTimestampSchema.nullable(),
|
|
10
|
+
}).superRefine((pause, context) => {
|
|
11
|
+
if (pause.status === 'ACTIVE' && pause.releasedAt !== null) {
|
|
12
|
+
context.addIssue({
|
|
13
|
+
code: 'custom', path: ['releasedAt'],
|
|
14
|
+
message: 'an active publication pause cannot have a release timestamp',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (pause.status === 'RELEASED' && pause.releasedAt === null) {
|
|
18
|
+
context.addIssue({
|
|
19
|
+
code: 'custom', path: ['releasedAt'],
|
|
20
|
+
message: 'a released publication pause requires a release timestamp',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (pause.releasedAt !== null && Date.parse(pause.releasedAt) < Date.parse(pause.claimedAt)) {
|
|
24
|
+
context.addIssue({
|
|
25
|
+
code: 'custom', path: ['releasedAt'],
|
|
26
|
+
message: 'a publication pause cannot be released before it was claimed',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
export function claimVoicePublicationPause({ current, agentScope, publicationOperationId, claimedAt, }) {
|
|
31
|
+
const scope = voiceAgentScopeSchema.parse(agentScope);
|
|
32
|
+
const operationId = voiceIdSchema.parse(publicationOperationId);
|
|
33
|
+
const timestamp = voiceTimestampSchema.parse(claimedAt);
|
|
34
|
+
if (current === null) {
|
|
35
|
+
return { ok: true, changed: true, pause: makePause({
|
|
36
|
+
scope, operationId, revision: 0, claimedAt: timestamp,
|
|
37
|
+
}) };
|
|
38
|
+
}
|
|
39
|
+
const existing = voicePublicationPauseSchema.parse(current);
|
|
40
|
+
if (!sameAgentScope({ left: existing, right: scope })) {
|
|
41
|
+
return { ok: false, reason: 'SCOPE_MISMATCH' };
|
|
42
|
+
}
|
|
43
|
+
if (existing.publicationOperationId === operationId) {
|
|
44
|
+
return { ok: true, pause: existing, changed: false };
|
|
45
|
+
}
|
|
46
|
+
if (existing.status === 'ACTIVE') {
|
|
47
|
+
return { ok: false, reason: 'OWNED_BY_ANOTHER_OPERATION' };
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, changed: true, pause: makePause({
|
|
50
|
+
scope, operationId, revision: existing.revision + 1, claimedAt: timestamp,
|
|
51
|
+
}) };
|
|
52
|
+
}
|
|
53
|
+
export function releaseVoicePublicationPause({ current, publicationOperationId, releasedAt, }) {
|
|
54
|
+
const existing = voicePublicationPauseSchema.parse(current);
|
|
55
|
+
const operationId = voiceIdSchema.parse(publicationOperationId);
|
|
56
|
+
const timestamp = voiceTimestampSchema.parse(releasedAt);
|
|
57
|
+
if (existing.publicationOperationId !== operationId) {
|
|
58
|
+
return { ok: false, reason: 'OWNED_BY_ANOTHER_OPERATION' };
|
|
59
|
+
}
|
|
60
|
+
if (existing.status === 'RELEASED') {
|
|
61
|
+
return { ok: true, pause: existing, changed: false };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
changed: true,
|
|
66
|
+
pause: voicePublicationPauseSchema.parse({
|
|
67
|
+
...existing, status: 'RELEASED', revision: existing.revision + 1,
|
|
68
|
+
releasedAt: timestamp,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export const voiceDependencyReferenceSchema = z.strictObject({
|
|
73
|
+
kind: z.literal('MANAGED_FUNCTION'),
|
|
74
|
+
managedFunctionId: voiceIdSchema,
|
|
75
|
+
observedVersionId: voiceIdSchema.nullable(),
|
|
76
|
+
});
|
|
77
|
+
export const voiceDependencyMaintenanceTargetSchema = z.discriminatedUnion('kind', [
|
|
78
|
+
z.strictObject({ kind: z.literal('ENVIRONMENT') }),
|
|
79
|
+
z.strictObject({
|
|
80
|
+
kind: z.literal('DEPENDENCIES'),
|
|
81
|
+
dependencies: z.array(voiceDependencyReferenceSchema).min(1),
|
|
82
|
+
affectedAgentIds: z.array(voiceIdSchema).min(1),
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
export const voiceDependencyMaintenanceFenceSchema = voiceScopeSchema.extend({
|
|
86
|
+
fenceId: voiceIdSchema,
|
|
87
|
+
target: voiceDependencyMaintenanceTargetSchema,
|
|
88
|
+
ownerPrincipalId: voiceIdSchema,
|
|
89
|
+
state: voiceDependencyMaintenanceStateSchema,
|
|
90
|
+
revision: voiceRevisionSchema,
|
|
91
|
+
createdAt: voiceTimestampSchema,
|
|
92
|
+
updatedAt: voiceTimestampSchema,
|
|
93
|
+
});
|
|
94
|
+
export const voiceAdmissionControlInputSchema = z.strictObject({
|
|
95
|
+
environmentEnabled: z.boolean(),
|
|
96
|
+
agentEnabled: z.boolean(),
|
|
97
|
+
routeEnabled: z.boolean(),
|
|
98
|
+
runtimeAccountEnabled: z.boolean(),
|
|
99
|
+
releaseReady: z.boolean(),
|
|
100
|
+
publicationPause: voicePublicationPauseSchema.nullable(),
|
|
101
|
+
dependencyMaintenanceBlocked: z.boolean(),
|
|
102
|
+
});
|
|
103
|
+
export const voiceAdmissionControlReasonSchema = z.enum([
|
|
104
|
+
'ENVIRONMENT_DISABLED', 'AGENT_DISABLED', 'ROUTE_DISABLED',
|
|
105
|
+
'RUNTIME_ACCOUNT_DISABLED', 'RELEASE_NOT_READY', 'PUBLICATION_PAUSED',
|
|
106
|
+
'DEPENDENCY_MAINTENANCE',
|
|
107
|
+
]);
|
|
108
|
+
export function evaluateVoiceAdmissionControl(input) {
|
|
109
|
+
const state = voiceAdmissionControlInputSchema.parse(input);
|
|
110
|
+
if (!state.environmentEnabled)
|
|
111
|
+
return { admitted: false, reason: 'ENVIRONMENT_DISABLED' };
|
|
112
|
+
if (!state.agentEnabled)
|
|
113
|
+
return { admitted: false, reason: 'AGENT_DISABLED' };
|
|
114
|
+
if (!state.routeEnabled)
|
|
115
|
+
return { admitted: false, reason: 'ROUTE_DISABLED' };
|
|
116
|
+
if (!state.runtimeAccountEnabled) {
|
|
117
|
+
return { admitted: false, reason: 'RUNTIME_ACCOUNT_DISABLED' };
|
|
118
|
+
}
|
|
119
|
+
if (!state.releaseReady)
|
|
120
|
+
return { admitted: false, reason: 'RELEASE_NOT_READY' };
|
|
121
|
+
if (state.publicationPause?.status === 'ACTIVE') {
|
|
122
|
+
return { admitted: false, reason: 'PUBLICATION_PAUSED' };
|
|
123
|
+
}
|
|
124
|
+
if (state.dependencyMaintenanceBlocked) {
|
|
125
|
+
return { admitted: false, reason: 'DEPENDENCY_MAINTENANCE' };
|
|
126
|
+
}
|
|
127
|
+
return { admitted: true };
|
|
128
|
+
}
|
|
129
|
+
function makePause({ scope, operationId, revision, claimedAt }) {
|
|
130
|
+
return voicePublicationPauseSchema.parse({
|
|
131
|
+
...scope, publicationOperationId: operationId, status: 'ACTIVE', revision,
|
|
132
|
+
claimedAt, releasedAt: null,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function sameAgentScope({ left, right }) {
|
|
136
|
+
return left.tenantId === right.tenantId &&
|
|
137
|
+
left.environmentId === right.environmentId && left.agentId === right.agentId;
|
|
138
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type VoiceCallSubject } from './voice-authority.js';
|
|
3
|
+
export declare const voiceReleaseLifecycleSchema: z.ZodObject<{
|
|
4
|
+
tenantId: z.ZodString;
|
|
5
|
+
environmentId: z.ZodString;
|
|
6
|
+
agentId: z.ZodString;
|
|
7
|
+
releaseId: z.ZodString;
|
|
8
|
+
publicationOperationId: z.ZodString;
|
|
9
|
+
state: z.ZodEnum<{
|
|
10
|
+
PREPARING: "PREPARING";
|
|
11
|
+
READY: "READY";
|
|
12
|
+
ACTIVE: "ACTIVE";
|
|
13
|
+
RETIRED: "RETIRED";
|
|
14
|
+
FAILED: "FAILED";
|
|
15
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
16
|
+
}>;
|
|
17
|
+
revision: z.ZodNumber;
|
|
18
|
+
createdAt: z.ZodISODateTime;
|
|
19
|
+
updatedAt: z.ZodISODateTime;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
export type VoiceReleaseLifecycle = z.infer<typeof voiceReleaseLifecycleSchema>;
|
|
22
|
+
export declare const voicePublicationOperationSchema: z.ZodObject<{
|
|
23
|
+
tenantId: z.ZodString;
|
|
24
|
+
environmentId: z.ZodString;
|
|
25
|
+
agentId: z.ZodString;
|
|
26
|
+
publicationOperationId: z.ZodString;
|
|
27
|
+
idempotencyKey: z.ZodString;
|
|
28
|
+
expectedDeploymentRevision: z.ZodNullable<z.ZodNumber>;
|
|
29
|
+
previousActiveReleaseId: z.ZodNullable<z.ZodString>;
|
|
30
|
+
candidateReleaseId: z.ZodNullable<z.ZodString>;
|
|
31
|
+
state: z.ZodEnum<{
|
|
32
|
+
PREPARING: "PREPARING";
|
|
33
|
+
FAILED: "FAILED";
|
|
34
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
35
|
+
WAITING_FOR_IDLE: "WAITING_FOR_IDLE";
|
|
36
|
+
ACTIVATING: "ACTIVATING";
|
|
37
|
+
SUCCEEDED: "SUCCEEDED";
|
|
38
|
+
}>;
|
|
39
|
+
revision: z.ZodNumber;
|
|
40
|
+
createdByPrincipalId: z.ZodString;
|
|
41
|
+
createdAt: z.ZodISODateTime;
|
|
42
|
+
updatedAt: z.ZodISODateTime;
|
|
43
|
+
}, z.core.$strict>;
|
|
44
|
+
export type VoicePublicationOperation = z.infer<typeof voicePublicationOperationSchema>;
|
|
45
|
+
export declare const voiceCallerIdentitySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
46
|
+
direction: z.ZodLiteral<"INBOUND">;
|
|
47
|
+
initiatingStaffPrincipalId: z.ZodNull;
|
|
48
|
+
phoneCaller: z.ZodObject<{
|
|
49
|
+
classification: z.ZodLiteral<"UNAUTHENTICATED_PHONE_CALLER">;
|
|
50
|
+
callerId: z.ZodNullable<z.ZodString>;
|
|
51
|
+
}, z.core.$strict>;
|
|
52
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
53
|
+
direction: z.ZodLiteral<"OUTBOUND">;
|
|
54
|
+
initiatingStaffPrincipalId: z.ZodString;
|
|
55
|
+
phoneCaller: z.ZodObject<{
|
|
56
|
+
classification: z.ZodLiteral<"REGISTERED_DESTINATION">;
|
|
57
|
+
destinationId: z.ZodString;
|
|
58
|
+
}, z.core.$strict>;
|
|
59
|
+
}, z.core.$strict>], "direction">;
|
|
60
|
+
export type VoiceCallerIdentity = z.infer<typeof voiceCallerIdentitySchema>;
|
|
61
|
+
export declare const voiceRuntimeBindingSchema: z.ZodObject<{
|
|
62
|
+
tenantId: z.ZodString;
|
|
63
|
+
environmentId: z.ZodString;
|
|
64
|
+
runtimeAccountId: z.ZodString;
|
|
65
|
+
observedRegistryUpdatedAt: z.ZodISODateTime;
|
|
66
|
+
checkedAt: z.ZodISODateTime;
|
|
67
|
+
accountStatus: z.ZodEnum<{
|
|
68
|
+
ACTIVE: "ACTIVE";
|
|
69
|
+
DISABLED: "DISABLED";
|
|
70
|
+
}>;
|
|
71
|
+
systemRunner: z.ZodLiteral<false>;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
export type VoiceRuntimeBinding = z.infer<typeof voiceRuntimeBindingSchema>;
|
|
74
|
+
export declare const voiceInboundRouteSchema: z.ZodObject<{
|
|
75
|
+
tenantId: z.ZodString;
|
|
76
|
+
environmentId: z.ZodString;
|
|
77
|
+
agentId: z.ZodString;
|
|
78
|
+
routeId: z.ZodString;
|
|
79
|
+
ownedPhoneNumberId: z.ZodString;
|
|
80
|
+
revision: z.ZodNumber;
|
|
81
|
+
enabled: z.ZodBoolean;
|
|
82
|
+
selectedByPrincipalId: z.ZodString;
|
|
83
|
+
updatedAt: z.ZodISODateTime;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
export type VoiceInboundRoute = z.infer<typeof voiceInboundRouteSchema>;
|
|
86
|
+
export declare const voiceSessionLifecycleSchema: z.ZodObject<{
|
|
87
|
+
tenantId: z.ZodString;
|
|
88
|
+
environmentId: z.ZodString;
|
|
89
|
+
agentId: z.ZodString;
|
|
90
|
+
sessionId: z.ZodString;
|
|
91
|
+
releaseId: z.ZodString;
|
|
92
|
+
routeRevision: z.ZodNullable<z.ZodNumber>;
|
|
93
|
+
policyRevision: z.ZodNumber;
|
|
94
|
+
callerIdentity: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
95
|
+
direction: z.ZodLiteral<"INBOUND">;
|
|
96
|
+
initiatingStaffPrincipalId: z.ZodNull;
|
|
97
|
+
phoneCaller: z.ZodObject<{
|
|
98
|
+
classification: z.ZodLiteral<"UNAUTHENTICATED_PHONE_CALLER">;
|
|
99
|
+
callerId: z.ZodNullable<z.ZodString>;
|
|
100
|
+
}, z.core.$strict>;
|
|
101
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
102
|
+
direction: z.ZodLiteral<"OUTBOUND">;
|
|
103
|
+
initiatingStaffPrincipalId: z.ZodString;
|
|
104
|
+
phoneCaller: z.ZodObject<{
|
|
105
|
+
classification: z.ZodLiteral<"REGISTERED_DESTINATION">;
|
|
106
|
+
destinationId: z.ZodString;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
}, z.core.$strict>], "direction">;
|
|
109
|
+
runtimeAccountId: z.ZodString;
|
|
110
|
+
systemRunner: z.ZodLiteral<false>;
|
|
111
|
+
state: z.ZodEnum<{
|
|
112
|
+
ACTIVE: "ACTIVE";
|
|
113
|
+
FAILED: "FAILED";
|
|
114
|
+
RESERVED: "RESERVED";
|
|
115
|
+
DISPATCHING: "DISPATCHING";
|
|
116
|
+
CONNECTING: "CONNECTING";
|
|
117
|
+
ENDING: "ENDING";
|
|
118
|
+
COMPLETED: "COMPLETED";
|
|
119
|
+
UNKNOWN: "UNKNOWN";
|
|
120
|
+
CANCELLING: "CANCELLING";
|
|
121
|
+
CANCELLED: "CANCELLED";
|
|
122
|
+
}>;
|
|
123
|
+
revision: z.ZodNumber;
|
|
124
|
+
admittedAt: z.ZodISODateTime;
|
|
125
|
+
updatedAt: z.ZodISODateTime;
|
|
126
|
+
}, z.core.$strict>;
|
|
127
|
+
export type VoiceSessionLifecycle = z.infer<typeof voiceSessionLifecycleSchema>;
|
|
128
|
+
export declare const voiceRegistrationSubjectSchema: z.ZodObject<{
|
|
129
|
+
tenantId: z.ZodString;
|
|
130
|
+
environmentId: z.ZodString;
|
|
131
|
+
agentId: z.ZodString;
|
|
132
|
+
sessionId: z.ZodString;
|
|
133
|
+
releaseId: z.ZodString;
|
|
134
|
+
}, z.core.$strict>;
|
|
135
|
+
export type VoiceRegistrationSubject = VoiceCallSubject;
|
|
136
|
+
export declare const voiceRegistrationOwnershipSchema: z.ZodObject<{
|
|
137
|
+
tenantId: z.ZodString;
|
|
138
|
+
environmentId: z.ZodString;
|
|
139
|
+
agentId: z.ZodString;
|
|
140
|
+
sessionId: z.ZodString;
|
|
141
|
+
releaseId: z.ZodString;
|
|
142
|
+
registrationOperationId: z.ZodString;
|
|
143
|
+
state: z.ZodEnum<{
|
|
144
|
+
FAILED: "FAILED";
|
|
145
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
146
|
+
SUCCEEDED: "SUCCEEDED";
|
|
147
|
+
CLAIMED: "CLAIMED";
|
|
148
|
+
}>;
|
|
149
|
+
revision: z.ZodNumber;
|
|
150
|
+
claimedAt: z.ZodISODateTime;
|
|
151
|
+
completedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
152
|
+
}, z.core.$strict>;
|
|
153
|
+
export type VoiceRegistrationOwnership = z.infer<typeof voiceRegistrationOwnershipSchema>;
|
|
154
|
+
type RegistrationClaimResult = {
|
|
155
|
+
ok: true;
|
|
156
|
+
registration: VoiceRegistrationOwnership;
|
|
157
|
+
changed: boolean;
|
|
158
|
+
} | {
|
|
159
|
+
ok: false;
|
|
160
|
+
reason: 'OWNED_BY_ANOTHER_OPERATION' | 'SUBJECT_MISMATCH';
|
|
161
|
+
};
|
|
162
|
+
export declare function claimVoiceRegistration({ current, subject, registrationOperationId, claimedAt, }: {
|
|
163
|
+
current: VoiceRegistrationOwnership | null;
|
|
164
|
+
subject: VoiceRegistrationSubject;
|
|
165
|
+
registrationOperationId: string;
|
|
166
|
+
claimedAt: string;
|
|
167
|
+
}): RegistrationClaimResult;
|
|
168
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { voiceCallSubjectSchema } from './voice-authority.js';
|
|
3
|
+
import { voiceAgentScopeSchema, voiceIdSchema, voiceRevisionSchema, voiceScopeSchema, voiceTimestampSchema, } from './voice-common.js';
|
|
4
|
+
import { voicePublicationStateSchema, voiceRegistrationStateSchema, voiceReleaseStateSchema, voiceSessionStateSchema, } from './voice-lifecycle-states.js';
|
|
5
|
+
export const voiceReleaseLifecycleSchema = voiceAgentScopeSchema.extend({
|
|
6
|
+
releaseId: voiceIdSchema,
|
|
7
|
+
publicationOperationId: voiceIdSchema,
|
|
8
|
+
state: voiceReleaseStateSchema,
|
|
9
|
+
revision: voiceRevisionSchema,
|
|
10
|
+
createdAt: voiceTimestampSchema,
|
|
11
|
+
updatedAt: voiceTimestampSchema,
|
|
12
|
+
});
|
|
13
|
+
export const voicePublicationOperationSchema = voiceAgentScopeSchema.extend({
|
|
14
|
+
publicationOperationId: voiceIdSchema,
|
|
15
|
+
idempotencyKey: voiceIdSchema,
|
|
16
|
+
expectedDeploymentRevision: voiceRevisionSchema.nullable(),
|
|
17
|
+
previousActiveReleaseId: voiceIdSchema.nullable(),
|
|
18
|
+
candidateReleaseId: voiceIdSchema.nullable(),
|
|
19
|
+
state: voicePublicationStateSchema,
|
|
20
|
+
revision: voiceRevisionSchema,
|
|
21
|
+
createdByPrincipalId: voiceIdSchema,
|
|
22
|
+
createdAt: voiceTimestampSchema,
|
|
23
|
+
updatedAt: voiceTimestampSchema,
|
|
24
|
+
});
|
|
25
|
+
export const voiceCallerIdentitySchema = z.discriminatedUnion('direction', [
|
|
26
|
+
z.strictObject({
|
|
27
|
+
direction: z.literal('INBOUND'),
|
|
28
|
+
initiatingStaffPrincipalId: z.null(),
|
|
29
|
+
phoneCaller: z.strictObject({
|
|
30
|
+
classification: z.literal('UNAUTHENTICATED_PHONE_CALLER'),
|
|
31
|
+
callerId: z.string().trim().min(1).max(64).nullable(),
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
z.strictObject({
|
|
35
|
+
direction: z.literal('OUTBOUND'),
|
|
36
|
+
initiatingStaffPrincipalId: voiceIdSchema,
|
|
37
|
+
phoneCaller: z.strictObject({
|
|
38
|
+
classification: z.literal('REGISTERED_DESTINATION'),
|
|
39
|
+
destinationId: voiceIdSchema,
|
|
40
|
+
}),
|
|
41
|
+
}),
|
|
42
|
+
]);
|
|
43
|
+
export const voiceRuntimeBindingSchema = voiceScopeSchema.extend({
|
|
44
|
+
runtimeAccountId: voiceIdSchema,
|
|
45
|
+
observedRegistryUpdatedAt: voiceTimestampSchema,
|
|
46
|
+
checkedAt: voiceTimestampSchema,
|
|
47
|
+
accountStatus: z.enum(['ACTIVE', 'DISABLED']),
|
|
48
|
+
systemRunner: z.literal(false),
|
|
49
|
+
});
|
|
50
|
+
export const voiceInboundRouteSchema = voiceAgentScopeSchema.extend({
|
|
51
|
+
routeId: voiceIdSchema,
|
|
52
|
+
ownedPhoneNumberId: voiceIdSchema,
|
|
53
|
+
revision: voiceRevisionSchema,
|
|
54
|
+
enabled: z.boolean(),
|
|
55
|
+
selectedByPrincipalId: voiceIdSchema,
|
|
56
|
+
updatedAt: voiceTimestampSchema,
|
|
57
|
+
});
|
|
58
|
+
export const voiceSessionLifecycleSchema = voiceCallSubjectSchema.extend({
|
|
59
|
+
routeRevision: voiceRevisionSchema.nullable(),
|
|
60
|
+
policyRevision: voiceRevisionSchema,
|
|
61
|
+
callerIdentity: voiceCallerIdentitySchema,
|
|
62
|
+
runtimeAccountId: voiceIdSchema,
|
|
63
|
+
systemRunner: z.literal(false),
|
|
64
|
+
state: voiceSessionStateSchema,
|
|
65
|
+
revision: voiceRevisionSchema,
|
|
66
|
+
admittedAt: voiceTimestampSchema,
|
|
67
|
+
updatedAt: voiceTimestampSchema,
|
|
68
|
+
});
|
|
69
|
+
export const voiceRegistrationSubjectSchema = voiceCallSubjectSchema;
|
|
70
|
+
export const voiceRegistrationOwnershipSchema = voiceRegistrationSubjectSchema.extend({
|
|
71
|
+
registrationOperationId: voiceIdSchema,
|
|
72
|
+
state: voiceRegistrationStateSchema,
|
|
73
|
+
revision: voiceRevisionSchema,
|
|
74
|
+
claimedAt: voiceTimestampSchema,
|
|
75
|
+
completedAt: voiceTimestampSchema.nullable(),
|
|
76
|
+
});
|
|
77
|
+
export function claimVoiceRegistration({ current, subject, registrationOperationId, claimedAt, }) {
|
|
78
|
+
const parsedSubject = voiceRegistrationSubjectSchema.parse({
|
|
79
|
+
tenantId: subject.tenantId,
|
|
80
|
+
environmentId: subject.environmentId,
|
|
81
|
+
agentId: subject.agentId,
|
|
82
|
+
sessionId: subject.sessionId,
|
|
83
|
+
releaseId: subject.releaseId,
|
|
84
|
+
});
|
|
85
|
+
const operationId = voiceIdSchema.parse(registrationOperationId);
|
|
86
|
+
const timestamp = voiceTimestampSchema.parse(claimedAt);
|
|
87
|
+
if (current !== null) {
|
|
88
|
+
const existing = voiceRegistrationOwnershipSchema.parse(current);
|
|
89
|
+
if (!sameSubject({ left: existing, right: parsedSubject })) {
|
|
90
|
+
return { ok: false, reason: 'SUBJECT_MISMATCH' };
|
|
91
|
+
}
|
|
92
|
+
if (existing.registrationOperationId !== operationId) {
|
|
93
|
+
return { ok: false, reason: 'OWNED_BY_ANOTHER_OPERATION' };
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, registration: existing, changed: false };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
changed: true,
|
|
100
|
+
registration: voiceRegistrationOwnershipSchema.parse({
|
|
101
|
+
...parsedSubject,
|
|
102
|
+
registrationOperationId: operationId,
|
|
103
|
+
state: 'CLAIMED',
|
|
104
|
+
revision: 0,
|
|
105
|
+
claimedAt: timestamp,
|
|
106
|
+
completedAt: null,
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function sameSubject({ left, right }) {
|
|
111
|
+
return left.tenantId === right.tenantId &&
|
|
112
|
+
left.environmentId === right.environmentId && left.agentId === right.agentId &&
|
|
113
|
+
left.sessionId === right.sessionId && left.releaseId === right.releaseId;
|
|
114
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const voiceCallDirectionSchema: z.ZodEnum<{
|
|
3
|
+
INBOUND: "INBOUND";
|
|
4
|
+
OUTBOUND: "OUTBOUND";
|
|
5
|
+
}>;
|
|
6
|
+
export type VoiceCallDirection = z.infer<typeof voiceCallDirectionSchema>;
|
|
7
|
+
export declare const voiceReleaseStateSchema: z.ZodEnum<{
|
|
8
|
+
PREPARING: "PREPARING";
|
|
9
|
+
READY: "READY";
|
|
10
|
+
ACTIVE: "ACTIVE";
|
|
11
|
+
RETIRED: "RETIRED";
|
|
12
|
+
FAILED: "FAILED";
|
|
13
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
14
|
+
}>;
|
|
15
|
+
export type VoiceReleaseState = z.infer<typeof voiceReleaseStateSchema>;
|
|
16
|
+
export declare const voicePublicationStateSchema: z.ZodEnum<{
|
|
17
|
+
PREPARING: "PREPARING";
|
|
18
|
+
FAILED: "FAILED";
|
|
19
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
20
|
+
WAITING_FOR_IDLE: "WAITING_FOR_IDLE";
|
|
21
|
+
ACTIVATING: "ACTIVATING";
|
|
22
|
+
SUCCEEDED: "SUCCEEDED";
|
|
23
|
+
}>;
|
|
24
|
+
export type VoicePublicationState = z.infer<typeof voicePublicationStateSchema>;
|
|
25
|
+
export declare const voiceSessionStateSchema: z.ZodEnum<{
|
|
26
|
+
ACTIVE: "ACTIVE";
|
|
27
|
+
FAILED: "FAILED";
|
|
28
|
+
RESERVED: "RESERVED";
|
|
29
|
+
DISPATCHING: "DISPATCHING";
|
|
30
|
+
CONNECTING: "CONNECTING";
|
|
31
|
+
ENDING: "ENDING";
|
|
32
|
+
COMPLETED: "COMPLETED";
|
|
33
|
+
UNKNOWN: "UNKNOWN";
|
|
34
|
+
CANCELLING: "CANCELLING";
|
|
35
|
+
CANCELLED: "CANCELLED";
|
|
36
|
+
}>;
|
|
37
|
+
export type VoiceSessionState = z.infer<typeof voiceSessionStateSchema>;
|
|
38
|
+
export declare const voiceRegistrationStateSchema: z.ZodEnum<{
|
|
39
|
+
FAILED: "FAILED";
|
|
40
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
41
|
+
SUCCEEDED: "SUCCEEDED";
|
|
42
|
+
CLAIMED: "CLAIMED";
|
|
43
|
+
}>;
|
|
44
|
+
export type VoiceRegistrationState = z.infer<typeof voiceRegistrationStateSchema>;
|
|
45
|
+
export declare const voiceDependencyMaintenanceStateSchema: z.ZodEnum<{
|
|
46
|
+
RECONCILIATION_REQUIRED: "RECONCILIATION_REQUIRED";
|
|
47
|
+
DRAINING: "DRAINING";
|
|
48
|
+
MAINTENANCE: "MAINTENANCE";
|
|
49
|
+
VERIFYING: "VERIFYING";
|
|
50
|
+
REPUBLICATION_REQUIRED: "REPUBLICATION_REQUIRED";
|
|
51
|
+
RELEASED: "RELEASED";
|
|
52
|
+
}>;
|
|
53
|
+
export type VoiceDependencyMaintenanceState = z.infer<typeof voiceDependencyMaintenanceStateSchema>;
|
|
54
|
+
type TransitionFailure = {
|
|
55
|
+
ok: false;
|
|
56
|
+
reason: 'TERMINAL_STATE' | 'RECONCILIATION_REQUIRED' | 'INVALID_TRANSITION';
|
|
57
|
+
};
|
|
58
|
+
type TransitionSuccess<TState> = {
|
|
59
|
+
ok: true;
|
|
60
|
+
state: TState;
|
|
61
|
+
changed: boolean;
|
|
62
|
+
};
|
|
63
|
+
export type VoiceReleaseTransition = TransitionSuccess<VoiceReleaseState> | TransitionFailure;
|
|
64
|
+
export type VoicePublicationTransition = TransitionSuccess<VoicePublicationState> | TransitionFailure;
|
|
65
|
+
export type VoiceSessionTransition = TransitionSuccess<VoiceSessionState> | TransitionFailure;
|
|
66
|
+
export type VoiceRegistrationTransition = TransitionSuccess<VoiceRegistrationState> | TransitionFailure;
|
|
67
|
+
export type VoiceDependencyMaintenanceTransition = TransitionSuccess<VoiceDependencyMaintenanceState> | TransitionFailure;
|
|
68
|
+
export declare function transitionVoiceReleaseState({ from, to, reconciled }: {
|
|
69
|
+
from: VoiceReleaseState;
|
|
70
|
+
to: VoiceReleaseState;
|
|
71
|
+
reconciled?: boolean;
|
|
72
|
+
}): VoiceReleaseTransition;
|
|
73
|
+
export declare function transitionVoicePublicationState({ from, to, reconciled }: {
|
|
74
|
+
from: VoicePublicationState;
|
|
75
|
+
to: VoicePublicationState;
|
|
76
|
+
reconciled?: boolean;
|
|
77
|
+
}): VoicePublicationTransition;
|
|
78
|
+
export declare function transitionVoiceSessionState({ from, to, reconciled }: {
|
|
79
|
+
from: VoiceSessionState;
|
|
80
|
+
to: VoiceSessionState;
|
|
81
|
+
reconciled?: boolean;
|
|
82
|
+
}): VoiceSessionTransition;
|
|
83
|
+
export declare function transitionVoiceRegistrationState({ from, to, reconciled }: {
|
|
84
|
+
from: VoiceRegistrationState;
|
|
85
|
+
to: VoiceRegistrationState;
|
|
86
|
+
reconciled?: boolean;
|
|
87
|
+
}): VoiceRegistrationTransition;
|
|
88
|
+
export declare function transitionVoiceDependencyMaintenanceFence({ from, to, reconciled, }: {
|
|
89
|
+
from: VoiceDependencyMaintenanceState;
|
|
90
|
+
to: VoiceDependencyMaintenanceState;
|
|
91
|
+
reconciled?: boolean;
|
|
92
|
+
}): VoiceDependencyMaintenanceTransition;
|
|
93
|
+
export declare function isVoiceSessionReservationHeld({ state }: {
|
|
94
|
+
state: VoiceSessionState;
|
|
95
|
+
}): boolean;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const voiceCallDirectionSchema = z.enum(['INBOUND', 'OUTBOUND']);
|
|
3
|
+
export const voiceReleaseStateSchema = z.enum([
|
|
4
|
+
'PREPARING', 'READY', 'ACTIVE', 'RETIRED', 'FAILED', 'RECONCILIATION_REQUIRED',
|
|
5
|
+
]);
|
|
6
|
+
export const voicePublicationStateSchema = z.enum([
|
|
7
|
+
'WAITING_FOR_IDLE', 'PREPARING', 'ACTIVATING', 'SUCCEEDED', 'FAILED',
|
|
8
|
+
'RECONCILIATION_REQUIRED',
|
|
9
|
+
]);
|
|
10
|
+
export const voiceSessionStateSchema = z.enum([
|
|
11
|
+
'RESERVED', 'DISPATCHING', 'CONNECTING', 'ACTIVE', 'ENDING', 'COMPLETED',
|
|
12
|
+
'FAILED', 'UNKNOWN', 'CANCELLING', 'CANCELLED',
|
|
13
|
+
]);
|
|
14
|
+
export const voiceRegistrationStateSchema = z.enum([
|
|
15
|
+
'CLAIMED', 'SUCCEEDED', 'FAILED', 'RECONCILIATION_REQUIRED',
|
|
16
|
+
]);
|
|
17
|
+
export const voiceDependencyMaintenanceStateSchema = z.enum([
|
|
18
|
+
'DRAINING', 'MAINTENANCE', 'VERIFYING', 'REPUBLICATION_REQUIRED', 'RELEASED',
|
|
19
|
+
'RECONCILIATION_REQUIRED',
|
|
20
|
+
]);
|
|
21
|
+
const releaseTransitions = {
|
|
22
|
+
PREPARING: ['READY', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
23
|
+
READY: ['ACTIVE', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
24
|
+
ACTIVE: ['RETIRED'],
|
|
25
|
+
RETIRED: [],
|
|
26
|
+
FAILED: [],
|
|
27
|
+
RECONCILIATION_REQUIRED: ['PREPARING', 'READY', 'FAILED'],
|
|
28
|
+
};
|
|
29
|
+
const publicationTransitions = {
|
|
30
|
+
WAITING_FOR_IDLE: ['PREPARING', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
31
|
+
PREPARING: ['ACTIVATING', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
32
|
+
ACTIVATING: ['SUCCEEDED', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
33
|
+
SUCCEEDED: [],
|
|
34
|
+
FAILED: [],
|
|
35
|
+
RECONCILIATION_REQUIRED: [
|
|
36
|
+
'WAITING_FOR_IDLE', 'PREPARING', 'ACTIVATING', 'SUCCEEDED', 'FAILED',
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
const sessionTransitions = {
|
|
40
|
+
RESERVED: ['DISPATCHING', 'CONNECTING', 'CANCELLING', 'FAILED', 'UNKNOWN'],
|
|
41
|
+
DISPATCHING: ['CONNECTING', 'CANCELLING', 'FAILED', 'UNKNOWN'],
|
|
42
|
+
CONNECTING: ['ACTIVE', 'CANCELLING', 'FAILED', 'UNKNOWN'],
|
|
43
|
+
ACTIVE: ['ENDING', 'CANCELLING', 'FAILED', 'UNKNOWN'],
|
|
44
|
+
ENDING: ['COMPLETED', 'CANCELLING', 'FAILED', 'UNKNOWN'],
|
|
45
|
+
COMPLETED: [],
|
|
46
|
+
FAILED: [],
|
|
47
|
+
UNKNOWN: [
|
|
48
|
+
'CONNECTING', 'ACTIVE', 'ENDING', 'COMPLETED', 'FAILED', 'CANCELLING',
|
|
49
|
+
'CANCELLED',
|
|
50
|
+
],
|
|
51
|
+
CANCELLING: ['CANCELLED', 'COMPLETED', 'UNKNOWN'],
|
|
52
|
+
CANCELLED: [],
|
|
53
|
+
};
|
|
54
|
+
const registrationTransitions = {
|
|
55
|
+
CLAIMED: ['SUCCEEDED', 'FAILED', 'RECONCILIATION_REQUIRED'],
|
|
56
|
+
SUCCEEDED: [],
|
|
57
|
+
FAILED: [],
|
|
58
|
+
RECONCILIATION_REQUIRED: ['CLAIMED', 'SUCCEEDED', 'FAILED'],
|
|
59
|
+
};
|
|
60
|
+
const maintenanceTransitions = {
|
|
61
|
+
DRAINING: ['MAINTENANCE', 'RECONCILIATION_REQUIRED'],
|
|
62
|
+
MAINTENANCE: ['VERIFYING', 'RECONCILIATION_REQUIRED'],
|
|
63
|
+
VERIFYING: ['REPUBLICATION_REQUIRED', 'RECONCILIATION_REQUIRED'],
|
|
64
|
+
REPUBLICATION_REQUIRED: ['RELEASED', 'RECONCILIATION_REQUIRED'],
|
|
65
|
+
RELEASED: [],
|
|
66
|
+
RECONCILIATION_REQUIRED: [
|
|
67
|
+
'DRAINING', 'MAINTENANCE', 'VERIFYING', 'REPUBLICATION_REQUIRED', 'RELEASED',
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
export function transitionVoiceReleaseState({ from, to, reconciled = false }) {
|
|
71
|
+
const result = transitionState({
|
|
72
|
+
from: voiceReleaseStateSchema.parse(from),
|
|
73
|
+
to: voiceReleaseStateSchema.parse(to),
|
|
74
|
+
reconciled,
|
|
75
|
+
transitions: releaseTransitions,
|
|
76
|
+
terminalStates: ['RETIRED', 'FAILED'],
|
|
77
|
+
});
|
|
78
|
+
return result.ok ? { ...result, state: voiceReleaseStateSchema.parse(result.state) } : result;
|
|
79
|
+
}
|
|
80
|
+
export function transitionVoicePublicationState({ from, to, reconciled = false }) {
|
|
81
|
+
const result = transitionState({
|
|
82
|
+
from: voicePublicationStateSchema.parse(from),
|
|
83
|
+
to: voicePublicationStateSchema.parse(to),
|
|
84
|
+
reconciled,
|
|
85
|
+
transitions: publicationTransitions,
|
|
86
|
+
terminalStates: ['SUCCEEDED', 'FAILED'],
|
|
87
|
+
});
|
|
88
|
+
return result.ok ? { ...result, state: voicePublicationStateSchema.parse(result.state) } : result;
|
|
89
|
+
}
|
|
90
|
+
export function transitionVoiceSessionState({ from, to, reconciled = false }) {
|
|
91
|
+
const result = transitionState({
|
|
92
|
+
from: voiceSessionStateSchema.parse(from),
|
|
93
|
+
to: voiceSessionStateSchema.parse(to),
|
|
94
|
+
reconciled,
|
|
95
|
+
transitions: sessionTransitions,
|
|
96
|
+
terminalStates: ['COMPLETED', 'FAILED', 'CANCELLED'],
|
|
97
|
+
reconciliationState: 'UNKNOWN',
|
|
98
|
+
});
|
|
99
|
+
return result.ok ? { ...result, state: voiceSessionStateSchema.parse(result.state) } : result;
|
|
100
|
+
}
|
|
101
|
+
export function transitionVoiceRegistrationState({ from, to, reconciled = false }) {
|
|
102
|
+
const result = transitionState({
|
|
103
|
+
from: voiceRegistrationStateSchema.parse(from),
|
|
104
|
+
to: voiceRegistrationStateSchema.parse(to),
|
|
105
|
+
reconciled,
|
|
106
|
+
transitions: registrationTransitions,
|
|
107
|
+
terminalStates: ['SUCCEEDED', 'FAILED'],
|
|
108
|
+
});
|
|
109
|
+
return result.ok ? { ...result, state: voiceRegistrationStateSchema.parse(result.state) } : result;
|
|
110
|
+
}
|
|
111
|
+
export function transitionVoiceDependencyMaintenanceFence({ from, to, reconciled = false, }) {
|
|
112
|
+
const result = transitionState({
|
|
113
|
+
from: voiceDependencyMaintenanceStateSchema.parse(from),
|
|
114
|
+
to: voiceDependencyMaintenanceStateSchema.parse(to),
|
|
115
|
+
reconciled,
|
|
116
|
+
transitions: maintenanceTransitions,
|
|
117
|
+
terminalStates: ['RELEASED'],
|
|
118
|
+
});
|
|
119
|
+
return result.ok
|
|
120
|
+
? { ...result, state: voiceDependencyMaintenanceStateSchema.parse(result.state) }
|
|
121
|
+
: result;
|
|
122
|
+
}
|
|
123
|
+
export function isVoiceSessionReservationHeld({ state }) {
|
|
124
|
+
const current = voiceSessionStateSchema.parse(state);
|
|
125
|
+
return !['COMPLETED', 'FAILED', 'CANCELLED'].includes(current);
|
|
126
|
+
}
|
|
127
|
+
function transitionState({ from, to, reconciled, transitions, terminalStates, reconciliationState = 'RECONCILIATION_REQUIRED', }) {
|
|
128
|
+
if (from === to)
|
|
129
|
+
return { ok: true, state: to, changed: false };
|
|
130
|
+
if (terminalStates.includes(from))
|
|
131
|
+
return { ok: false, reason: 'TERMINAL_STATE' };
|
|
132
|
+
if (from === reconciliationState && !reconciled) {
|
|
133
|
+
return { ok: false, reason: 'RECONCILIATION_REQUIRED' };
|
|
134
|
+
}
|
|
135
|
+
if (!(transitions[from] ?? []).includes(to)) {
|
|
136
|
+
return { ok: false, reason: 'INVALID_TRANSITION' };
|
|
137
|
+
}
|
|
138
|
+
return { ok: true, state: to, changed: true };
|
|
139
|
+
}
|