@automate.ax/api-contract 0.57.3 → 0.58.0

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/dist/index.d.ts CHANGED
@@ -1111,6 +1111,7 @@ export declare const contract: {
1111
1111
  key: import("zod").ZodOptional<import("zod").ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
1112
1112
  outcomeSeq: import("zod").ZodNumber;
1113
1113
  policy: import("zod").ZodLiteral<"collect">;
1114
+ position: import("zod").ZodOptional<import("zod").ZodNumber>;
1114
1115
  primaryPosition: import("zod").ZodEnum<{
1115
1116
  first: "first";
1116
1117
  last: "last";
@@ -1129,6 +1130,42 @@ export declare const contract: {
1129
1130
  streamName: import("zod").ZodString;
1130
1131
  streamNames: import("zod").ZodArray<import("zod").ZodString>;
1131
1132
  ttl: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1133
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
1134
+ actionDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1135
+ eventDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1136
+ scopePath: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodNumber>>;
1137
+ signalDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1138
+ slot: import("zod").ZodNumber;
1139
+ count: import("zod").ZodNumber;
1140
+ outcomeSeq: import("zod").ZodNumber;
1141
+ policy: import("zod").ZodLiteral<"fanout">;
1142
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
1143
+ actionDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1144
+ eventDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1145
+ scopePath: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodNumber>>;
1146
+ signalDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1147
+ slot: import("zod").ZodNumber;
1148
+ key: import("zod").ZodOptional<import("zod").ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
1149
+ maxBurstDuration: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1150
+ minGap: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1151
+ minQuietPeriod: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1152
+ outcomeSeq: import("zod").ZodNumber;
1153
+ policy: import("zod").ZodLiteral<"funnel">;
1154
+ primaryPosition: import("zod").ZodEnum<{
1155
+ first: "first";
1156
+ last: "last";
1157
+ }>;
1158
+ selection: import("zod").ZodEnum<{
1159
+ first: "first";
1160
+ last: "last";
1161
+ all: "all";
1162
+ }>;
1163
+ triggerAt: import("zod").ZodEnum<{
1164
+ start: "start";
1165
+ end: "end";
1166
+ both: "both";
1167
+ }>;
1168
+ ttl: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1132
1169
  }, import("zod/v4/core").$strip>, import("zod").ZodUnion<readonly [import("zod").ZodObject<{
1133
1170
  actionDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1134
1171
  eventDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
@@ -1169,22 +1206,7 @@ export declare const contract: {
1169
1206
  scopeOrigins: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodString>>;
1170
1207
  selectedIndex: import("zod").ZodNumber;
1171
1208
  status: import("zod").ZodLiteral<"succeeded">;
1172
- }, import("zod/v4/core").$strip>]>, import("zod").ZodObject<{
1173
- actionDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1174
- eventDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1175
- scopePath: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodNumber>>;
1176
- signalDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
1177
- slot: import("zod").ZodNumber;
1178
- duration: import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>;
1179
- key: import("zod").ZodOptional<import("zod").ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
1180
- outcomeSeq: import("zod").ZodNumber;
1181
- policy: import("zod").ZodLiteral<"window">;
1182
- primaryPosition: import("zod").ZodEnum<{
1183
- first: "first";
1184
- last: "last";
1185
- }>;
1186
- ttl: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
1187
- }, import("zod/v4/core").$strip>]>>>;
1209
+ }, import("zod/v4/core").$strip>]>]>>>;
1188
1210
  settled: import("zod").ZodDefault<import("zod").ZodBoolean>;
1189
1211
  signalInvocations: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodUnion<readonly [import("zod").ZodObject<{
1190
1212
  actionDependencyIds: import("zod").ZodArray<import("zod").ZodString>;
@@ -1740,11 +1762,22 @@ export declare const contract: {
1740
1762
  }, import("zod/v4/core").$strip>;
1741
1763
  outputIndex: import("zod").ZodNumber;
1742
1764
  }, import("zod/v4/core").$strip>, import("zod").ZodVoid, Record<never, never>, Record<never, never>>;
1743
- sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
1765
+ sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodIntersection<import("zod").ZodObject<{
1744
1766
  subject: import("zod").ZodString;
1745
- text: import("zod").ZodString;
1746
1767
  to: import("zod").ZodDefault<import("zod").ZodUnion<readonly [import("zod").ZodEmail, import("zod").ZodLiteral<"*">]>>;
1768
+ }, import("zod/v4/core").$strip>, import("zod").ZodUnion<readonly [import("zod").ZodObject<{
1769
+ html: import("zod").ZodString;
1770
+ markdown: import("zod").ZodOptional<import("zod").ZodString>;
1771
+ text: import("zod").ZodOptional<import("zod").ZodString>;
1772
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
1773
+ html: import("zod").ZodOptional<import("zod").ZodString>;
1774
+ markdown: import("zod").ZodString;
1775
+ text: import("zod").ZodOptional<import("zod").ZodString>;
1747
1776
  }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
1777
+ html: import("zod").ZodOptional<import("zod").ZodString>;
1778
+ markdown: import("zod").ZodOptional<import("zod").ZodString>;
1779
+ text: import("zod").ZodString;
1780
+ }, import("zod/v4/core").$strip>]>>, import("zod").ZodObject<{
1748
1781
  id: import("zod").ZodNullable<import("zod").ZodString>;
1749
1782
  }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
1750
1783
  };
package/dist/runtime.d.ts CHANGED
@@ -243,6 +243,7 @@ export declare const runtimeContract: {
243
243
  key: z.ZodOptional<z.ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
244
244
  outcomeSeq: z.ZodNumber;
245
245
  policy: z.ZodLiteral<"collect">;
246
+ position: z.ZodOptional<z.ZodNumber>;
246
247
  primaryPosition: z.ZodEnum<{
247
248
  first: "first";
248
249
  last: "last";
@@ -261,6 +262,42 @@ export declare const runtimeContract: {
261
262
  streamName: z.ZodString;
262
263
  streamNames: z.ZodArray<z.ZodString>;
263
264
  ttl: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
265
+ }, z.core.$strip>, z.ZodObject<{
266
+ actionDependencyIds: z.ZodArray<z.ZodString>;
267
+ eventDependencyIds: z.ZodArray<z.ZodString>;
268
+ scopePath: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
269
+ signalDependencyIds: z.ZodArray<z.ZodString>;
270
+ slot: z.ZodNumber;
271
+ count: z.ZodNumber;
272
+ outcomeSeq: z.ZodNumber;
273
+ policy: z.ZodLiteral<"fanout">;
274
+ }, z.core.$strip>, z.ZodObject<{
275
+ actionDependencyIds: z.ZodArray<z.ZodString>;
276
+ eventDependencyIds: z.ZodArray<z.ZodString>;
277
+ scopePath: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
278
+ signalDependencyIds: z.ZodArray<z.ZodString>;
279
+ slot: z.ZodNumber;
280
+ key: z.ZodOptional<z.ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
281
+ maxBurstDuration: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
282
+ minGap: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
283
+ minQuietPeriod: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
284
+ outcomeSeq: z.ZodNumber;
285
+ policy: z.ZodLiteral<"funnel">;
286
+ primaryPosition: z.ZodEnum<{
287
+ first: "first";
288
+ last: "last";
289
+ }>;
290
+ selection: z.ZodEnum<{
291
+ first: "first";
292
+ last: "last";
293
+ all: "all";
294
+ }>;
295
+ triggerAt: z.ZodEnum<{
296
+ start: "start";
297
+ end: "end";
298
+ both: "both";
299
+ }>;
300
+ ttl: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
264
301
  }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
265
302
  actionDependencyIds: z.ZodArray<z.ZodString>;
266
303
  eventDependencyIds: z.ZodArray<z.ZodString>;
@@ -301,22 +338,7 @@ export declare const runtimeContract: {
301
338
  scopeOrigins: z.ZodDefault<z.ZodArray<z.ZodString>>;
302
339
  selectedIndex: z.ZodNumber;
303
340
  status: z.ZodLiteral<"succeeded">;
304
- }, z.core.$strip>]>, z.ZodObject<{
305
- actionDependencyIds: z.ZodArray<z.ZodString>;
306
- eventDependencyIds: z.ZodArray<z.ZodString>;
307
- scopePath: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
308
- signalDependencyIds: z.ZodArray<z.ZodString>;
309
- slot: z.ZodNumber;
310
- duration: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
311
- key: z.ZodOptional<z.ZodCustom<import("@automate.ax/codec").Encodable, import("@automate.ax/codec").Encodable>>;
312
- outcomeSeq: z.ZodNumber;
313
- policy: z.ZodLiteral<"window">;
314
- primaryPosition: z.ZodEnum<{
315
- first: "first";
316
- last: "last";
317
- }>;
318
- ttl: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
319
- }, z.core.$strip>]>>>;
341
+ }, z.core.$strip>]>]>>>;
320
342
  settled: z.ZodDefault<z.ZodBoolean>;
321
343
  signalInvocations: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
322
344
  actionDependencyIds: z.ZodArray<z.ZodString>;
@@ -872,11 +894,22 @@ export declare const runtimeContract: {
872
894
  }, z.core.$strip>;
873
895
  outputIndex: z.ZodNumber;
874
896
  }, z.core.$strip>, z.ZodVoid, Record<never, never>, Record<never, never>>;
875
- sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
897
+ sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodIntersection<z.ZodObject<{
876
898
  subject: z.ZodString;
877
- text: z.ZodString;
878
899
  to: z.ZodDefault<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"*">]>>;
900
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
901
+ html: z.ZodString;
902
+ markdown: z.ZodOptional<z.ZodString>;
903
+ text: z.ZodOptional<z.ZodString>;
904
+ }, z.core.$strip>, z.ZodObject<{
905
+ html: z.ZodOptional<z.ZodString>;
906
+ markdown: z.ZodString;
907
+ text: z.ZodOptional<z.ZodString>;
879
908
  }, z.core.$strip>, z.ZodObject<{
909
+ html: z.ZodOptional<z.ZodString>;
910
+ markdown: z.ZodOptional<z.ZodString>;
911
+ text: z.ZodString;
912
+ }, z.core.$strip>]>>, z.ZodObject<{
880
913
  id: z.ZodNullable<z.ZodString>;
881
914
  }, z.core.$strip>, Record<never, never>, Record<never, never>>;
882
915
  };
package/dist/runtime.js CHANGED
@@ -25,6 +25,23 @@ const AUTOMATION_INVOCATION_RUN_IN_SCHEMA = z.union([
25
25
  z.string().min(1),
26
26
  z.number().nonnegative(),
27
27
  ]);
28
+ const EMAIL_BODY_SCHEMA = z.union([
29
+ z.object({
30
+ html: z.string().min(1),
31
+ markdown: z.string().min(1).optional(),
32
+ text: z.string().min(1).optional(),
33
+ }),
34
+ z.object({
35
+ html: z.string().min(1).optional(),
36
+ markdown: z.string().min(1),
37
+ text: z.string().min(1).optional(),
38
+ }),
39
+ z.object({
40
+ html: z.string().min(1).optional(),
41
+ markdown: z.string().min(1).optional(),
42
+ text: z.string().min(1),
43
+ }),
44
+ ]);
28
45
  /** Immediate or mutually exclusive absolute/relative invocation timing. */
29
46
  export const automationInvocationScheduleSchema = z.union([
30
47
  z.object({
@@ -120,6 +137,10 @@ const SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA = z.object({
120
137
  slot: z.number().int().nonnegative(),
121
138
  });
122
139
  const COORDINATION_PRIMARY_POSITION_SCHEMA = z.enum(["first", "last"]);
140
+ const COORDINATION_DURATION_SCHEMA = z.union([
141
+ z.string().min(1),
142
+ z.number().nonnegative(),
143
+ ]);
123
144
  const CORRELATION_STREAM_NAMES_SCHEMA = z
124
145
  .string()
125
146
  .min(1)
@@ -159,16 +180,51 @@ const COLLECTION_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
159
180
  key: encodableSchema,
160
181
  outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
161
182
  policy: z.literal("collect"),
183
+ position: z.number().int().nonnegative().optional(),
162
184
  primaryPosition: COORDINATION_PRIMARY_POSITION_SCHEMA,
163
185
  ttl: z.union([z.string().min(1), z.number().nonnegative()]).optional(),
164
186
  });
165
- const WINDOW_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
166
- duration: z.union([z.string().min(1), z.number().nonnegative()]),
187
+ const FANOUT_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
188
+ count: z.number().int().nonnegative(),
189
+ outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
190
+ policy: z.literal("fanout"),
191
+ });
192
+ const FUNNEL_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
167
193
  key: encodableSchema,
194
+ maxBurstDuration: COORDINATION_DURATION_SCHEMA.optional(),
195
+ minGap: COORDINATION_DURATION_SCHEMA.optional(),
196
+ minQuietPeriod: COORDINATION_DURATION_SCHEMA.optional(),
168
197
  outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
169
- policy: z.literal("window"),
198
+ policy: z.literal("funnel"),
170
199
  primaryPosition: COORDINATION_PRIMARY_POSITION_SCHEMA,
171
- ttl: z.union([z.string().min(1), z.number().nonnegative()]).optional(),
200
+ selection: z.enum(["all", "first", "last"]),
201
+ triggerAt: z.enum(["start", "end", "both"]),
202
+ ttl: COORDINATION_DURATION_SCHEMA.optional(),
203
+ }).superRefine((offer, context) => {
204
+ if (offer.triggerAt === "end" &&
205
+ offer.minGap !== undefined &&
206
+ offer.minQuietPeriod === undefined &&
207
+ offer.maxBurstDuration === undefined) {
208
+ context.addIssue({
209
+ code: "custom",
210
+ message: "A trailing funnel with minGap also requires minQuietPeriod or maxBurstDuration.",
211
+ path: ["minGap"],
212
+ });
213
+ }
214
+ if (offer.selection !== "all" && offer.selection !== offer.primaryPosition) {
215
+ context.addIssue({
216
+ code: "custom",
217
+ message: "A selecting funnel must continue from its selected occurrence.",
218
+ path: ["primaryPosition"],
219
+ });
220
+ }
221
+ if (offer.triggerAt === "start" && offer.selection !== "first") {
222
+ context.addIssue({
223
+ code: "custom",
224
+ message: "A start-only funnel can only select its first occurrence.",
225
+ path: ["selection"],
226
+ });
227
+ }
172
228
  });
173
229
  const RACE_OFFER_BASE_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
174
230
  inputOrigins: z.string().array().array().min(1),
@@ -480,8 +536,9 @@ export const runtimeContract = {
480
536
  .union([
481
537
  COLLECTION_OFFER_SCHEMA,
482
538
  CORRELATION_OFFER_SCHEMA,
539
+ FANOUT_OFFER_SCHEMA,
540
+ FUNNEL_OFFER_SCHEMA,
483
541
  RACE_OFFER_SCHEMA,
484
- WINDOW_OFFER_SCHEMA,
485
542
  ])
486
543
  .array()
487
544
  .default([]),
@@ -547,11 +604,12 @@ export const runtimeContract = {
547
604
  }))
548
605
  .output(z.void()),
549
606
  sendEmail: oc
550
- .input(z.object({
607
+ .input(z
608
+ .object({
551
609
  subject: z.string().min(1),
552
- text: z.string().min(1),
553
610
  to: z.union([z.email(), z.literal("*")]).default("*"),
554
- }))
611
+ })
612
+ .and(EMAIL_BODY_SCHEMA))
555
613
  .output(z.object({
556
614
  id: z.string().nullable(),
557
615
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/api-contract",
3
- "version": "0.57.3",
3
+ "version": "0.58.0",
4
4
  "description": "Public oRPC contract for the Automate.ax API.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@ai-sdk/gateway": "^4.0.46",
31
- "@automate.ax/codec": "0.57.3",
31
+ "@automate.ax/codec": "0.58.0",
32
32
  "@orpc/contract": "1.15.0",
33
33
  "zod": "^4.3.6"
34
34
  },
package/src/runtime.ts CHANGED
@@ -31,6 +31,23 @@ const AUTOMATION_INVOCATION_RUN_IN_SCHEMA = z.union([
31
31
  z.string().min(1),
32
32
  z.number().nonnegative(),
33
33
  ])
34
+ const EMAIL_BODY_SCHEMA = z.union([
35
+ z.object({
36
+ html: z.string().min(1),
37
+ markdown: z.string().min(1).optional(),
38
+ text: z.string().min(1).optional(),
39
+ }),
40
+ z.object({
41
+ html: z.string().min(1).optional(),
42
+ markdown: z.string().min(1),
43
+ text: z.string().min(1).optional(),
44
+ }),
45
+ z.object({
46
+ html: z.string().min(1).optional(),
47
+ markdown: z.string().min(1).optional(),
48
+ text: z.string().min(1),
49
+ }),
50
+ ])
34
51
 
35
52
  /** Immediate or mutually exclusive absolute/relative invocation timing. */
36
53
  export const automationInvocationScheduleSchema = z.union([
@@ -137,6 +154,10 @@ const SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA = z.object({
137
154
  })
138
155
 
139
156
  const COORDINATION_PRIMARY_POSITION_SCHEMA = z.enum(["first", "last"])
157
+ const COORDINATION_DURATION_SCHEMA = z.union([
158
+ z.string().min(1),
159
+ z.number().nonnegative(),
160
+ ])
140
161
  const CORRELATION_STREAM_NAMES_SCHEMA = z
141
162
  .string()
142
163
  .min(1)
@@ -180,17 +201,56 @@ const COLLECTION_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
180
201
  key: encodableSchema,
181
202
  outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
182
203
  policy: z.literal("collect"),
204
+ position: z.number().int().nonnegative().optional(),
183
205
  primaryPosition: COORDINATION_PRIMARY_POSITION_SCHEMA,
184
206
  ttl: z.union([z.string().min(1), z.number().nonnegative()]).optional(),
185
207
  })
186
208
 
187
- const WINDOW_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
188
- duration: z.union([z.string().min(1), z.number().nonnegative()]),
209
+ const FANOUT_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
210
+ count: z.number().int().nonnegative(),
211
+ outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
212
+ policy: z.literal("fanout"),
213
+ })
214
+
215
+ const FUNNEL_OFFER_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
189
216
  key: encodableSchema,
217
+ maxBurstDuration: COORDINATION_DURATION_SCHEMA.optional(),
218
+ minGap: COORDINATION_DURATION_SCHEMA.optional(),
219
+ minQuietPeriod: COORDINATION_DURATION_SCHEMA.optional(),
190
220
  outcomeSeq: OUTCOME_SEQUENCE_SCHEMA,
191
- policy: z.literal("window"),
221
+ policy: z.literal("funnel"),
192
222
  primaryPosition: COORDINATION_PRIMARY_POSITION_SCHEMA,
193
- ttl: z.union([z.string().min(1), z.number().nonnegative()]).optional(),
223
+ selection: z.enum(["all", "first", "last"]),
224
+ triggerAt: z.enum(["start", "end", "both"]),
225
+ ttl: COORDINATION_DURATION_SCHEMA.optional(),
226
+ }).superRefine((offer, context) => {
227
+ if (
228
+ offer.triggerAt === "end" &&
229
+ offer.minGap !== undefined &&
230
+ offer.minQuietPeriod === undefined &&
231
+ offer.maxBurstDuration === undefined
232
+ ) {
233
+ context.addIssue({
234
+ code: "custom",
235
+ message:
236
+ "A trailing funnel with minGap also requires minQuietPeriod or maxBurstDuration.",
237
+ path: ["minGap"],
238
+ })
239
+ }
240
+ if (offer.selection !== "all" && offer.selection !== offer.primaryPosition) {
241
+ context.addIssue({
242
+ code: "custom",
243
+ message: "A selecting funnel must continue from its selected occurrence.",
244
+ path: ["primaryPosition"],
245
+ })
246
+ }
247
+ if (offer.triggerAt === "start" && offer.selection !== "first") {
248
+ context.addIssue({
249
+ code: "custom",
250
+ message: "A start-only funnel can only select its first occurrence.",
251
+ path: ["selection"],
252
+ })
253
+ }
194
254
  })
195
255
 
196
256
  const RACE_OFFER_BASE_SCHEMA = SIGNAL_INVOCATION_DEPENDENCIES_SCHEMA.extend({
@@ -552,8 +612,9 @@ export const runtimeContract = {
552
612
  .union([
553
613
  COLLECTION_OFFER_SCHEMA,
554
614
  CORRELATION_OFFER_SCHEMA,
615
+ FANOUT_OFFER_SCHEMA,
616
+ FUNNEL_OFFER_SCHEMA,
555
617
  RACE_OFFER_SCHEMA,
556
- WINDOW_OFFER_SCHEMA,
557
618
  ])
558
619
  .array()
559
620
  .default([]),
@@ -633,11 +694,12 @@ export const runtimeContract = {
633
694
  .output(z.void()),
634
695
  sendEmail: oc
635
696
  .input(
636
- z.object({
637
- subject: z.string().min(1),
638
- text: z.string().min(1),
639
- to: z.union([z.email(), z.literal("*")]).default("*"),
640
- }),
697
+ z
698
+ .object({
699
+ subject: z.string().min(1),
700
+ to: z.union([z.email(), z.literal("*")]).default("*"),
701
+ })
702
+ .and(EMAIL_BODY_SCHEMA),
641
703
  )
642
704
  .output(
643
705
  z.object({