@cowliss/cli 0.3.0 → 0.5.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.
@@ -9,16 +9,6 @@ const DOCS_URL = "https://docs.cowliss.com";
9
9
  */
10
10
  const CLERK_ORG_SUBJECT_PREFIX = "org_";
11
11
  /**
12
- * The two fixed environments every org has (spec: Environments). An app
13
- * belongs to one, so the environment of every write is the app's;
14
- * profiles, identifiers, events, memberships, journey instances,
15
- * deliveries, violations, quarantine, the address ledger, and idempotency
16
- * keys are per environment, while the catalog, sending domains, the
17
- * suppression mirror, billing, and journey code are shared. A third
18
- * environment is a one-line change here plus the mirrored db enum.
19
- */
20
- const ENVIRONMENTS = ["development", "production"];
21
- /**
22
12
  * Fixed consent purposes for the prototype. Consent is a per-purpose map on
23
13
  * the profile, checked at send-step execution time.
24
14
  *
@@ -51,8 +41,8 @@ const EXECUTION_LIMITS = {
51
41
  logLines: 100,
52
42
  logLineBytes: 1024
53
43
  };
54
- /** Per release: manifest counts, bundle size, and the source tarball. */
55
- const RELEASE_LIMITS = {
44
+ /** Per push: manifest counts, bundle size, and the source tarball. */
45
+ const PUSH_LIMITS = {
56
46
  journeys: 100,
57
47
  templates: 200,
58
48
  bundleBytes: 2097152,
@@ -66,65 +56,13 @@ const RELEASE_LIMITS = {
66
56
  const COW_CONFIG_SCHEMA_PATH = "/schemas/cow.json";
67
57
  const COW_CONFIG_SCHEMA_URL = `${DOCS_URL}${COW_CONFIG_SCHEMA_PATH}`;
68
58
 
69
- //#endregion
70
- //#region ../../packages/shared/src/journeys-v2/config.ts
71
- /**
72
- * A project's name: what `cow init` asked for, and what the `projects` row
73
- * stores. Lives here rather than beside the release DTOs because `cow.json`
74
- * is the file a developer types it into; `createProjectBodySchema` reuses it.
75
- */
76
- const projectNameSchema = z.string().trim().min(1).max(200);
77
- /**
78
- * `cow.json` (spec: Project layout): the org, and which of its projects this
79
- * directory is. The environment is always a flag, and auth never lives in
80
- * the project. Strict, so a typo'd key is a build error rather than a
81
- * silently ignored setting.
82
- */
83
- const cowConfigSchema = z.strictObject({
84
- $schema: z.url().optional(),
85
- orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
86
- /**
87
- * Which project of the org this directory pushes to. One org can hold
88
- * several, one per repo, each with its own release sequence and its own
89
- * slice of the deployed journeys, so every project says which it is.
90
- */
91
- project: projectNameSchema,
92
- /** Overrides the API the CLI talks to; the hosted product needs none. */
93
- apiUrl: z.url().optional(),
94
- /**
95
- * Overrides the dashboard `cow login` opens. Paired with `apiUrl`: a
96
- * login against one Cowliss's dashboard yields a token the other's API
97
- * rejects, so a config that names an API names its dashboard too.
98
- */
99
- webUrl: z.url().optional()
100
- }).meta({
101
- title: "cow.json",
102
- description: "A cow project: the organization and the project it deploys to."
103
- });
104
-
105
- //#endregion
106
- //#region ../../packages/shared/src/environments.ts
107
- /**
108
- * The environment on the wire: the source's `environment` field, the
109
- * `X-Cow-Environment` header admin calls select with, and the
110
- * `environment` column every per-environment DTO carries.
111
- */
112
- const environmentSchema = z.enum(ENVIRONMENTS);
113
- /**
114
- * The environments a shared definition (a segment, a journey) declares it
115
- * works on: a non-empty list of distinct values. A definition declaring none
116
- * would be dead code with a row behind it, and a repeated value is a typo
117
- * rather than an intent, which is the rule `defineJourney` applies too.
118
- */
119
- const environmentsSchema = z.array(environmentSchema).min(1, "at least one environment is required").refine((values) => new Set(values).size === values.length, { message: "environments must not repeat" });
120
-
121
59
  //#endregion
122
60
  //#region ../../packages/shared/src/journeys-v2/manifest.ts
123
61
  /**
124
- * The release manifest (spec: Build; Push and compile): what `cow build`
62
+ * The pushed manifest (spec: Build; Push and compile): what `cow build`
125
63
  * extracts from a project and `cow push` uploads with the bundles. The
126
- * server validates it with these schemas, compiles every bundle, and stores
127
- * the compiled form on the release row.
64
+ * server validates it with these schemas, compiles every bundle whose key
65
+ * changed, and stores that entry on the version it creates.
128
66
  */
129
67
  /**
130
68
  * A journey or template key: the file basename under `journeys/` or
@@ -147,6 +85,50 @@ const journeyKeySchema = z.string().max(64).regex(JOURNEY_KEY_PATTERN, "key must
147
85
  */
148
86
  const tagsSchema = z.array(z.string().trim().min(1).max(50, "a tag must be at most 50 characters")).max(20, "a journey or template takes at most 20 tags").default([]).transform((tags) => [...new Set(tags)]);
149
87
  /**
88
+ * A consent purpose key: camelCase, matching the fixed `emailMarketing` and
89
+ * `dataProcessing`. Purposes are keys in the `consent` map a customer reads
90
+ * on their own profile, which is why they are not the kebab-case of a
91
+ * journey key.
92
+ */
93
+ const CONSENT_PURPOSE_KEY_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
94
+ /**
95
+ * One purpose key wherever a key is named rather than declared: a journey's
96
+ * `purpose`, and the keys of the consent patch an identify carries. The set
97
+ * a key is checked against is the org's declared rows, which no schema can
98
+ * see, so validation here is the shape only; the deploy and the ingestion
99
+ * write refuse a key the org has not declared.
100
+ */
101
+ const consentPurposeKeySchema = z.string().max(50, "a purpose key must be at most 50 characters").regex(CONSENT_PURPOSE_KEY_PATTERN, "a consent purpose key must be camelCase (a letter first, then letters and digits)");
102
+ /**
103
+ * One purpose a project declares in `cow.json` (spec: Decisions). A declared
104
+ * purpose is marketing-class and sits under the `emailMarketing` umbrella,
105
+ * so `denied` is the only default it may carry: the purpose is absent on
106
+ * every profile that already exists, and a granted default would answer for
107
+ * all of them at once.
108
+ *
109
+ * The field stays required rather than disappearing, so every `cow.json` and
110
+ * every stored manifest written before this still parses, and the column
111
+ * behind it still holds `granted` for the seeded `dataProcessing` row: this
112
+ * is a refusal at declaration time, not a narrower storage shape.
113
+ *
114
+ * The two fixed purposes cannot be declared. They are seeded for every org
115
+ * and owned by no project, so a project redeclaring one would be renaming
116
+ * the master switch every other project's journeys hang off.
117
+ */
118
+ const declaredPurposeSchema = z.strictObject({
119
+ key: consentPurposeKeySchema.refine((key) => !CONSENT_PURPOSES.includes(key), `${CONSENT_PURPOSES.join(" and ")} are fixed purposes and cannot be declared`),
120
+ /** What the dashboard and the account modal render beside the switch. */
121
+ label: z.string().trim().min(1).max(50, "a purpose label must be at most 50 characters"),
122
+ /** What the purpose means for a profile whose map does not answer it. */
123
+ default: z.literal("denied", "a declared purpose's \"default\" must be \"denied\": nobody has answered it yet, and a granted default would opt every profile you already have into it")
124
+ });
125
+ /**
126
+ * The purposes one project declares. Capped low the way tags are: a purpose
127
+ * is a category a recipient reads on a preferences switch, not a taxonomy.
128
+ * Absent means the project declares none, which is every project today.
129
+ */
130
+ const purposesSchema = z.array(declaredPurposeSchema).max(20, "a project declares at most 20 consent purposes");
131
+ /**
150
132
  * A matcher field: one pattern or a non-empty list of them, a list being a
151
133
  * disjunction. See `matchesPattern` in ../patterns for the dialect (`*`
152
134
  * only) and for why a pattern whose literal prefix is not `system.` never
@@ -160,7 +142,7 @@ const patternSchema = z.union([onePatternSchema, z.array(onePatternSchema).min(1
160
142
  * reuses it.
161
143
  *
162
144
  * Both members are strict, so a journey holding the retired `source` key
163
- * fails to compile a release instead of silently triggering on every app.
145
+ * fails to compile instead of silently triggering on every app.
164
146
  * There is deliberately no pipe filter: a trigger narrows by the app the
165
147
  * write is attributed to, the same token a segment definition names.
166
148
  */
@@ -168,14 +150,29 @@ const triggerSchema = z.union([z.strictObject({
168
150
  event: patternSchema,
169
151
  appId: patternSchema.optional()
170
152
  }), z.strictObject({ segment: z.string().min(1) })]);
153
+ /**
154
+ * A destination's name: the token a journey addresses it by, in a
155
+ * `send.webhook` call or a journey's `senderIdentity`.
156
+ *
157
+ * Defined here rather than beside the destinations contract, and imported
158
+ * from here by it, because a journey manifest names one and the guest layer
159
+ * is bundled into every tenant module: importing it the other way round
160
+ * would pull the Drizzle destinations table into all of them.
161
+ */
162
+ const destinationNameSchema = z.string().trim().min(1, "name is required").max(100, "name must be at most 100 characters");
171
163
  /** A content address: `sha256:` plus the lowercase hex digest. */
172
164
  const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
173
- /** The names of the capability calls a journey may make. */
165
+ /**
166
+ * The names of the capability calls a journey may make. Grouped by verb and
167
+ * then channel (`send.email`, not `email.send`), because the verb is the
168
+ * thing a journey author is choosing between and the name should read the
169
+ * way the code is written.
170
+ */
174
171
  const COMMAND_NAMES = [
175
172
  "sleep",
176
173
  "waitForEvent",
177
- "email.send",
178
- "webhook.send",
174
+ "send.email",
175
+ "send.webhook",
179
176
  "traits.set",
180
177
  "traits.unset",
181
178
  "profile.get",
@@ -184,23 +181,65 @@ const COMMAND_NAMES = [
184
181
  "restart"
185
182
  ];
186
183
  /**
187
- * One entry of the step spine `cow build` records by running `run` once
188
- * against a recording stub. Display only, never trusted: a journey's real
189
- * control flow is whatever its code does at runtime. `detail` names the
190
- * template, destination, event, or trait key when the call has one.
184
+ * What a spine entry can be: every capability call, plus the control flow
185
+ * `cow build` reads off the journey's source: an `if` (a condition, with
186
+ * `steps` and `otherwise`), a `loop` (with `steps` as its body), and the `end`
187
+ * of a path.
191
188
  */
189
+ const SPINE_ENTRY_NAMES = [
190
+ ...COMMAND_NAMES,
191
+ "if",
192
+ "loop",
193
+ "end"
194
+ ];
192
195
  const spineEntrySchema = z.object({
193
- name: z.enum(COMMAND_NAMES),
194
- detail: z.string().max(200).optional()
195
- });
196
+ name: z.enum(SPINE_ENTRY_NAMES),
197
+ detail: z.string().max(200).optional(),
198
+ /**
199
+ * The sender identity a `send.email` call named for itself, overriding
200
+ * the journey's own. Present only when the author wrote one on the call,
201
+ * which is what lets the deploy warning and the journey detail page name
202
+ * the override without re-reading the code.
203
+ */
204
+ senderIdentity: z.string().max(100).optional(),
205
+ /** A `waitForEvent` timeout, as the author wrote it. */
206
+ timeout: z.string().max(50).optional(),
207
+ get steps() {
208
+ return z.array(spineEntrySchema).optional();
209
+ },
210
+ get otherwise() {
211
+ return z.array(spineEntrySchema).optional();
212
+ }
213
+ }).meta({ id: "JourneySpineEntry" });
214
+ /**
215
+ * One journey in a stored manifest. A plain (non-strict) object on purpose:
216
+ * a manifest pushed before the author's rollout gate went away still carries
217
+ * `environments`, and every stored manifest has to keep parsing for good
218
+ * (ADR 0011). Zod strips the key, and the journey runs wherever the
219
+ * per-environment enabled flag says it runs, which is the one gate there is.
220
+ */
196
221
  const manifestJourneySchema = z.object({
197
222
  key: journeyKeySchema,
198
223
  /** The author's labels; the dashboard's only grouping. */
199
224
  tags: tagsSchema,
200
225
  trigger: triggerSchema,
201
- purpose: z.enum(CONSENT_PURPOSES),
202
- /** The author's rollout gate: the journey is active only in these. */
203
- environments: environmentsSchema,
226
+ /**
227
+ * A fixed purpose or one the project declares. Checked against the org's
228
+ * declared set at push time, not here: a manifest is built and pushed
229
+ * without ever reaching the org whose rows say what exists.
230
+ */
231
+ purpose: consentPurposeKeySchema,
232
+ /**
233
+ * The sender identity every `send.email` in this journey goes out as,
234
+ * unless the call names its own. A name, never a `dst_` id: an org has one
235
+ * row per environment, so an id would send in production and fail in
236
+ * development, which is the one thing a journey must not do.
237
+ *
238
+ * Optional here and required at `defineJourney`, exactly like `purposes`: a
239
+ * version pushed before the field existed carries none and its stored
240
+ * manifest still parses. The author's build is where the error is useful.
241
+ */
242
+ senderIdentity: destinationNameSchema.optional(),
204
243
  spine: z.array(spineEntrySchema),
205
244
  bundle: digestSchema
206
245
  });
@@ -230,40 +269,87 @@ function uniqueKeys(items, ctx, path) {
230
269
  seen.add(item.key);
231
270
  }
232
271
  }
233
- /** What `cow build` writes to `.cow/build/manifest.json` and `cow push` sends. */
272
+ /**
273
+ * What `cow build` writes to `.cow/build/manifest.json`, and the shape a
274
+ * push row stores for good, entry by entry, on the versions it creates.
275
+ *
276
+ * `protocol` is any positive integer rather than the current constant on
277
+ * purpose: a version is immutable and an execution stays pinned to the one
278
+ * it started on, so the day the protocol is bumped every stored push must
279
+ * still parse, or the runner and every API response the client validates
280
+ * break at once for any org with history. The literal lives at push time
281
+ * only (`createPushBodySchema`), which is where a stale CLI is the
282
+ * developer's own fixable problem, and the runner refuses a version built
283
+ * for another protocol with a message naming both numbers.
284
+ */
234
285
  const manifestSchema = z.object({
235
- protocol: z.literal(1),
286
+ protocol: z.number().int().positive(),
236
287
  /** The `@cowliss/cli` version the project was built with. */
237
288
  sdk: z.string().min(1),
238
- journeys: z.array(manifestJourneySchema).max(RELEASE_LIMITS.journeys),
239
- templates: z.array(manifestTemplateSchema).max(RELEASE_LIMITS.templates),
289
+ journeys: z.array(manifestJourneySchema).max(PUSH_LIMITS.journeys),
290
+ templates: z.array(manifestTemplateSchema).max(PUSH_LIMITS.templates),
291
+ /**
292
+ * The consent purposes this project declares, copied from `cow.json`.
293
+ * Optional rather than defaulted: a push made before purposes existed
294
+ * carries none, and its stored manifest still parses.
295
+ */
296
+ purposes: purposesSchema.optional(),
240
297
  /** Digest of the gzipped source tarball. */
241
298
  source: digestSchema
242
299
  }).superRefine((manifest, ctx) => {
243
300
  uniqueKeys(manifest.journeys, ctx, "journeys");
244
301
  uniqueKeys(manifest.templates, ctx, "templates");
302
+ uniqueKeys(manifest.purposes ?? [], ctx, "purposes");
245
303
  });
246
304
  /**
247
- * The manifest as the release row stores it once compilation succeeded:
248
- * the pushed manifest plus the compiled module digest per key, and the
249
- * digest of the Javy engine plugin the toolchain that compiled them was
250
- * built from.
251
- *
252
- * Journeys and templates are keyed separately because they share a key
253
- * space: `welcome.ts` and `welcome.tsx` are one journey and the email it
254
- * sends in every example, and a flat map would let one overwrite the other.
255
- *
256
- * `plugin` is a toolchain record, not a linked artifact: modules are
257
- * statically linked, so the plugin bytes are inside each module. It says
258
- * which engine compiled the release, which is what a later bug report or a
259
- * reproducible rebuild needs.
305
+ * One entry of a stored manifest: what a version is a snapshot of. Journeys
306
+ * and templates are kept apart because they share a key space (`welcome.ts`
307
+ * and `welcome.tsx` are one journey and the email it sends in every
308
+ * example), and the version's own `kind` says which of the two this is.
260
309
  */
261
- const compiledManifestSchema = manifestSchema.safeExtend({
262
- modules: z.object({
263
- journeys: z.record(journeyKeySchema, digestSchema),
264
- templates: z.record(journeyKeySchema, digestSchema)
265
- }),
266
- plugin: digestSchema
310
+ const versionManifestSchema = z.union([manifestJourneySchema, manifestTemplateSchema]);
311
+
312
+ //#endregion
313
+ //#region ../../packages/shared/src/journeys-v2/config.ts
314
+ /**
315
+ * A project's name: what `cow init` asked for, and what the `projects` row
316
+ * stores. Lives here rather than beside the release DTOs because `cow.json`
317
+ * is the file a developer types it into; `createProjectBodySchema` reuses it.
318
+ */
319
+ const projectNameSchema = z.string().trim().min(1).max(200);
320
+ /**
321
+ * `cow.json` (spec: Project layout): the org, and which of its projects this
322
+ * directory is. The environment is always a flag, and auth never lives in
323
+ * the project. Strict, so a typo'd key is a build error rather than a
324
+ * silently ignored setting.
325
+ */
326
+ const cowConfigSchema = z.strictObject({
327
+ $schema: z.url().optional(),
328
+ orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
329
+ /**
330
+ * Which project of the org this directory pushes to. One org can hold
331
+ * several, one per repo, each with its own release sequence and its own
332
+ * slice of the deployed journeys, so every project says which it is.
333
+ */
334
+ project: projectNameSchema,
335
+ /**
336
+ * The consent purposes this project declares. They are org-wide, so two
337
+ * projects declaring one key must agree on its label and default or the
338
+ * deploy is refused; a deploy adds and updates them and never deletes
339
+ * one, because profiles hold answers against them.
340
+ */
341
+ purposes: purposesSchema.optional(),
342
+ /** Overrides the API the CLI talks to; the hosted product needs none. */
343
+ apiUrl: z.url().optional(),
344
+ /**
345
+ * Overrides the dashboard `cow login` opens. Paired with `apiUrl`: a
346
+ * login against one Cowliss's dashboard yields a token the other's API
347
+ * rejects, so a config that names an API names its dashboard too.
348
+ */
349
+ webUrl: z.url().optional()
350
+ }).meta({
351
+ title: "cow.json",
352
+ description: "A cow project: the organization and the project it pushes to."
267
353
  });
268
354
 
269
355
  //#endregion
@@ -308,14 +394,21 @@ const commandSchema = z.discriminatedUnion("name", [
308
394
  })
309
395
  }),
310
396
  z.object({
311
- name: z.literal("email.send"),
397
+ name: z.literal("send.email"),
312
398
  args: z.strictObject({
313
399
  template: journeyKeySchema,
314
- props: properties
400
+ props: properties,
401
+ /**
402
+ * Which sender identity this mail leaves as, by name. Required, and
403
+ * the guest SDK fills in the journey's own when the call does not name
404
+ * one, so the host has one resolution path and never has to read the
405
+ * manifest to find a sender.
406
+ */
407
+ senderIdentity: destinationNameSchema
315
408
  })
316
409
  }),
317
410
  z.object({
318
- name: z.literal("webhook.send"),
411
+ name: z.literal("send.webhook"),
319
412
  args: z.strictObject({
320
413
  destination: z.string().min(1),
321
414
  payload: properties
@@ -392,7 +485,7 @@ const executionLimitsSchema = z.object({
392
485
  logLineBytes: z.number().int().positive()
393
486
  });
394
487
  const journeyStepInputSchema = z.object({
395
- protocol: z.literal(1),
488
+ protocol: z.literal(2),
396
489
  kind: z.literal("journey"),
397
490
  key: journeyKeySchema,
398
491
  event: guestEventSchema,
@@ -432,7 +525,7 @@ const journeyStepOutputSchema = z.discriminatedUnion("status", [
432
525
  })
433
526
  ]);
434
527
  const templateRenderInputSchema = z.object({
435
- protocol: z.literal(1),
528
+ protocol: z.literal(2),
436
529
  kind: z.literal("template"),
437
530
  key: journeyKeySchema,
438
531
  props: properties
@@ -451,7 +544,7 @@ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
451
544
  const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
452
545
  trigger: true,
453
546
  purpose: true,
454
- environments: true,
547
+ senderIdentity: true,
455
548
  tags: true
456
549
  }).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
457
550
  sendClass: true,
@@ -563,22 +656,29 @@ var CapabilityError = class extends Error {
563
656
  };
564
657
  /**
565
658
  * The config half of a journey, validated with the same schema the release
566
- * manifest is validated with, so a bad trigger or an unknown purpose fails
567
- * at build time rather than at the first execution.
659
+ * manifest is validated with, so a bad trigger or a malformed purpose key
660
+ * fails at build time rather than at the first execution. Whether the org
661
+ * declares that purpose is a question only the deploy can answer.
568
662
  */
569
663
  const journeyConfigSchema = manifestJourneySchema.pick({
570
664
  trigger: true,
571
665
  purpose: true,
572
- environments: true,
666
+ senderIdentity: true,
573
667
  tags: true
574
- });
575
- /** Author a journey. Throws at definition time on an invalid config. */
668
+ }).extend({ senderIdentity: destinationNameSchema });
669
+ /**
670
+ * Author a journey. Throws at definition time on an invalid config.
671
+ *
672
+ * There is no environment gate here: a journey runs wherever it is enabled,
673
+ * and nowhere else (ADR 0011). A source that still passes `environments` is
674
+ * a typecheck error in `cow build`, which is where the author can fix it.
675
+ */
576
676
  function defineJourney(input) {
577
677
  return {
578
678
  ...journeyConfigSchema.parse({
579
679
  trigger: input.trigger,
580
680
  purpose: input.purpose,
581
- environments: input.environments ?? [...ENVIRONMENTS],
681
+ senderIdentity: input.senderIdentity,
582
682
  tags: input.tags
583
683
  }),
584
684
  run: input.run
@@ -1,5 +1,4 @@
1
- import { n as Duration, r as GuestEvent, t as Trigger } from "./index-heC1gFE_.js";
2
- import { n as Environment, t as ConsentPurpose } from "./constants-OZrYz4I2.js";
1
+ import { n as Duration, r as GuestEvent, t as Trigger } from "./index-BDj02EsQ.js";
3
2
  //#region src/guest/journeys.d.ts
4
3
  /** The event a journey runs for, or the one a `waitForEvent` resolved with. */
5
4
  type Event = GuestEvent;
@@ -13,12 +12,12 @@ type Profile = {
13
12
  id: string;
14
13
  traits: Record<string, unknown>;
15
14
  /** Consent state per purpose; the send gates read it host-side too. */
16
- consent: Record<ConsentPurpose, boolean>;
15
+ consent: Record<string, boolean>;
17
16
  identifiers: Record<string, string>;
18
17
  /** Names of the segments the profile is currently in. */
19
18
  segments: string[];
20
19
  };
21
- /** What `api.email.send` resolves to once the host accepted the send. */
20
+ /** What `api.send.email` resolves to once the host accepted the send. */
22
21
  type EmailSendResult = {
23
22
  deliveryId: string;
24
23
  status: string;
@@ -35,7 +34,7 @@ declare class CapabilityError extends Error {
35
34
  /**
36
35
  * The project's templates, keyed by template key. `cow build` writes the
37
36
  * real interface into `.cow/types.d.ts` and TypeScript merges it into this
38
- * one, which is what types `api.email.send`. Empty here on purpose: a
37
+ * one, which is what types `api.send.email`. Empty here on purpose: a
39
38
  * project that has not built yet still compiles, with string keys.
40
39
  */
41
40
  interface CowTemplates {}
@@ -60,14 +59,22 @@ type Api = {
60
59
  waitForEvent(pattern: string | string[], options: {
61
60
  timeout: Duration;
62
61
  }): Promise<Event | null>;
63
- email: {
64
- send<Key extends TemplateKey>(args: {
62
+ /**
63
+ * The two things a journey sends. Grouped by the verb rather than by the
64
+ * channel, because the verb is what an author is choosing between.
65
+ */
66
+ send: {
67
+ email<Key extends TemplateKey>(args: {
65
68
  template: Key;
66
69
  props: TemplateProps<Key>;
70
+ /**
71
+ * Send this one mail from a different sender identity than the
72
+ * journey's own. A name the organization has configured, resolved in
73
+ * the environment the execution is running in.
74
+ */
75
+ senderIdentity?: string;
67
76
  }): Promise<EmailSendResult>;
68
- };
69
- webhook: {
70
- send(args: {
77
+ webhook(args: {
71
78
  destination: string;
72
79
  payload: Record<string, unknown>;
73
80
  }): Promise<void>;
@@ -108,18 +115,37 @@ type Api = {
108
115
  */
109
116
  type JourneyConfig = {
110
117
  trigger: Trigger;
111
- purpose: ConsentPurpose;
112
- /** The author's rollout gate; defaults to every environment. */
113
- environments: Environment[];
118
+ /**
119
+ * A fixed purpose (`emailMarketing`, `dataProcessing`) or one this
120
+ * project declares in `cow.json`. The key's shape is checked here; that
121
+ * the org declares it is checked when the release is deployed.
122
+ */
123
+ purpose: string;
124
+ /**
125
+ * The sender identity every `api.send.email` in this journey goes out as:
126
+ * the name of one your organization has configured, or `cowliss-default`
127
+ * for the address Cowliss gives you with nothing to set up. A single send
128
+ * can override it.
129
+ *
130
+ * Required, and named here rather than in a settings page, so the address
131
+ * a journey sends from is readable in the journey's own source. It is
132
+ * resolved per environment, which is why it is a name and not an id.
133
+ */
134
+ senderIdentity: string;
114
135
  /** The author's labels; the dashboard's only grouping. Defaults to none. */
115
136
  tags: string[];
116
137
  };
117
138
  type Journey = JourneyConfig & {
118
139
  run: (event: Event, api: Api) => Promise<void>;
119
140
  };
120
- /** Author a journey. Throws at definition time on an invalid config. */
121
- declare function defineJourney(input: Omit<JourneyConfig, "environments" | "tags"> & {
122
- environments?: Environment[];
141
+ /**
142
+ * Author a journey. Throws at definition time on an invalid config.
143
+ *
144
+ * There is no environment gate here: a journey runs wherever it is enabled,
145
+ * and nowhere else (ADR 0011). A source that still passes `environments` is
146
+ * a typecheck error in `cow build`, which is where the author can fix it.
147
+ */
148
+ declare function defineJourney(input: Omit<JourneyConfig, "tags"> & {
123
149
  tags?: string[];
124
150
  run: (event: Event, api: Api) => Promise<void>;
125
151
  }): Journey;
@@ -1,3 +1,3 @@
1
- import { n as defineJourney, t as CapabilityError } from "./journeys-BcbIxxI-.js";
1
+ import { n as defineJourney, t as CapabilityError } from "./journeys-C8j8Sqvl.js";
2
2
 
3
3
  export { CapabilityError, defineJourney };
@@ -1,4 +1,4 @@
1
- import { t as runGuest } from "./driver-M6LQVMr2.js";
1
+ import { t as runGuest } from "./driver-14FnzM-h.js";
2
2
 
3
3
  //#region src/guest/wasi.ts
4
4
  const STDIN = 0;