@cowliss/cli 0.4.0 → 0.6.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/README.md CHANGED
@@ -26,7 +26,7 @@ cow whoami
26
26
  ```
27
27
 
28
28
  `cow login` opens the dashboard and caches a session token. In CI, set
29
- `COW_DEPLOY_KEY` to a deploy key instead.
29
+ `COW_PIPELINE_KEY` to a pipeline key instead.
30
30
 
31
31
  ## Journeys and emails as code
32
32
 
@@ -45,7 +45,6 @@ import { defineJourney } from "@cowliss/cli/journeys";
45
45
  export default defineJourney({
46
46
  trigger: { event: "checkout_started" },
47
47
  purpose: "emailMarketing",
48
- senderIdentity: "cowliss-default",
49
48
  run: async (event, api) => {
50
49
  const purchased = await api.waitForEvent("purchase_completed", {
51
50
  timeout: "24h",
@@ -70,16 +69,22 @@ cow test abandoned-checkout --scenario scenarios/cart.json
70
69
  # typecheck and bundle, no API call
71
70
  cow build
72
71
 
73
- # publish it and make it the environment's live code
74
- cow deploy --env development
72
+ # upload the project; the server compiles it
73
+ cow push
75
74
 
76
- # rebuild and redeploy on every save
77
- cow dev
75
+ # what the server holds, and what is running
76
+ cow status
77
+
78
+ # turn it on
79
+ cow enable abandoned-checkout
78
80
  ```
79
81
 
80
- Every push creates a numbered release. `cow deploy` makes one release an
81
- environment's current code, `cow rollback` puts the previous one back, and a
82
- run that has already started stays on the release it started on.
82
+ A push creates a new version of every journey and template whose code
83
+ changed, and changes no flag: `cow enable` and `cow disable` are the only
84
+ things that put code in front of anyone. A journey runs its latest version
85
+ that finished compiling, so a push that fails to compile leaves the previous
86
+ one running, and a run that has already started stays on the version it
87
+ started on. To undo, `git revert` and push again.
83
88
 
84
89
  ## Admin, and everything else
85
90
 
@@ -1,4 +1,4 @@
1
- import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-B_xg_GnL.js";
1
+ import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-BrcKEXz0.js";
2
2
  import { z } from "zod";
3
3
  import { createElement } from "react";
4
4
  import { renderToStaticMarkup } from "react-dom/server.browser";
@@ -124,7 +124,8 @@ function createApi(input, journey) {
124
124
  args: {
125
125
  template: args.template,
126
126
  props: args.props,
127
- senderIdentity: args.senderIdentity ?? journey.senderIdentity
127
+ from: args.from ?? journey.from,
128
+ replyTo: args.replyTo
128
129
  }
129
130
  }),
130
131
  webhook: (args) => call({
@@ -5572,8 +5573,7 @@ function readManifest(module) {
5572
5573
  kind: "journey",
5573
5574
  trigger: journey.trigger,
5574
5575
  purpose: journey.purpose,
5575
- senderIdentity: journey.senderIdentity,
5576
- environments: journey.environments,
5576
+ from: journey.from,
5577
5577
  tags: journey.tags
5578
5578
  });
5579
5579
  }
@@ -1,4 +1,4 @@
1
- import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-DAGLEHDn.js";
1
+ import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-CXcCEcAg.js";
2
2
  //#region src/guest/driver.d.ts
3
3
  /**
4
4
  * The in-process driver: JSON in, JSON out (spec: Guest protocol). One
@@ -1,3 +1,3 @@
1
- import { t as runGuest } from "./driver-DaNdNhuF.js";
1
+ import { t as runGuest } from "./driver-DBt5l1Z5.js";
2
2
 
3
3
  export { runGuest };
@@ -1,6 +1,23 @@
1
- import { n as SendClass } from "./constants-B0wk-87t.js";
2
1
  import { z } from "zod";
3
2
  import { ReactElement } from "react";
3
+ //#region ../../packages/shared/src/constants.d.ts
4
+ /**
5
+ * The two classes of send, declared on the template rather than passed per
6
+ * call so the class cannot drift between two sends of the same message.
7
+ *
8
+ * Marketing is everything a journey sends on the org's behalf: it carries
9
+ * the RFC 8058 unsubscribe headers and runs the full gate list.
10
+ * Transactional is the developer's own operational mail (a receipt, an
11
+ * export-is-ready notice): it carries neither header and skips consent and
12
+ * the per-user frequency cap, because an unsubscribe link on an invoice is
13
+ * wrong and a receipt must not silently vanish for anyone who once left a
14
+ * newsletter. Suppression, the quota, the sending pause, and the from-domain
15
+ * check still apply to both: those bound cost and protect the shared SES
16
+ * account, and none of them are about what the recipient asked for.
17
+ */
18
+ declare const SEND_CLASSES: readonly ["marketing", "transactional"];
19
+ type SendClass = (typeof SEND_CLASSES)[number];
20
+ //#endregion
4
21
  //#region src/guest/emails.d.ts
5
22
  /** A template's subject line, computed from the props it declares. */
6
23
  type Subject<Schema extends z.ZodType> = (props: z.infer<Schema>) => string;
@@ -34,7 +34,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
34
34
  args: z.ZodObject<{
35
35
  template: z.ZodString;
36
36
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
37
- senderIdentity: z.ZodString;
37
+ from: z.ZodOptional<z.ZodString>;
38
+ replyTo: z.ZodOptional<z.ZodEmail>;
38
39
  }, z.core.$strict>;
39
40
  }, z.core.$strip>, z.ZodObject<{
40
41
  name: z.ZodLiteral<"send.webhook">;
@@ -124,7 +125,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
124
125
  args: z.ZodObject<{
125
126
  template: z.ZodString;
126
127
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
127
- senderIdentity: z.ZodString;
128
+ from: z.ZodOptional<z.ZodString>;
129
+ replyTo: z.ZodOptional<z.ZodEmail>;
128
130
  }, z.core.$strict>;
129
131
  }, z.core.$strip>, z.ZodObject<{
130
132
  name: z.ZodLiteral<"send.webhook">;
@@ -183,7 +185,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
185
  args: z.ZodObject<{
184
186
  template: z.ZodString;
185
187
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
186
- senderIdentity: z.ZodString;
188
+ from: z.ZodOptional<z.ZodString>;
189
+ replyTo: z.ZodOptional<z.ZodEmail>;
187
190
  }, z.core.$strict>;
188
191
  }, z.core.$strip>, z.ZodObject<{
189
192
  name: z.ZodLiteral<"send.webhook">;
@@ -257,11 +260,7 @@ declare const manifestOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
257
260
  segment: z.ZodString;
258
261
  }, z.core.$strict>]>;
259
262
  purpose: z.ZodString;
260
- senderIdentity: z.ZodOptional<z.ZodString>;
261
- environments: z.ZodArray<z.ZodEnum<{
262
- development: "development";
263
- production: "production";
264
- }>>;
263
+ from: z.ZodOptional<z.ZodString>;
265
264
  kind: z.ZodLiteral<"journey">;
266
265
  }, z.core.$strip>, z.ZodObject<{
267
266
  tags: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
@@ -282,7 +281,7 @@ type ManifestOutput = z.infer<typeof manifestOutputSchema>;
282
281
  * reuses it.
283
282
  *
284
283
  * Both members are strict, so a journey holding the retired `source` key
285
- * fails to compile a release instead of silently triggering on every app.
284
+ * fails to compile instead of silently triggering on every app.
286
285
  * There is deliberately no pipe filter: a trigger narrows by the app the
287
286
  * write is attributed to, the same token a segment definition names.
288
287
  */
@@ -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,29 +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/environments.ts
71
- /**
72
- * The environment on the wire: the source's `environment` field, the
73
- * `X-Cow-Environment` header admin calls select with, and the
74
- * `environment` column every per-environment DTO carries.
75
- */
76
- const environmentSchema = z.enum(ENVIRONMENTS);
77
- /**
78
- * The environments a shared definition (a segment, a journey) declares it
79
- * works on: a non-empty list of distinct values. A definition declaring none
80
- * would be dead code with a row behind it, and a repeated value is a typo
81
- * rather than an intent, which is the rule `defineJourney` applies too.
82
- */
83
- 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" });
84
-
85
59
  //#endregion
86
60
  //#region ../../packages/shared/src/journeys-v2/manifest.ts
87
61
  /**
88
- * The release manifest (spec: Build; Push and compile): what `cow build`
62
+ * The pushed manifest (spec: Build; Push and compile): what `cow build`
89
63
  * extracts from a project and `cow push` uploads with the bundles. The
90
- * server validates it with these schemas, compiles every bundle, and stores
91
- * 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.
92
66
  */
93
67
  /**
94
68
  * A journey or template key: the file basename under `journeys/` or
@@ -168,7 +142,7 @@ const patternSchema = z.union([onePatternSchema, z.array(onePatternSchema).min(1
168
142
  * reuses it.
169
143
  *
170
144
  * Both members are strict, so a journey holding the retired `source` key
171
- * fails to compile a release instead of silently triggering on every app.
145
+ * fails to compile instead of silently triggering on every app.
172
146
  * There is deliberately no pipe filter: a trigger narrows by the app the
173
147
  * write is attributed to, the same token a segment definition names.
174
148
  */
@@ -177,15 +151,44 @@ const triggerSchema = z.union([z.strictObject({
177
151
  appId: patternSchema.optional()
178
152
  }), z.strictObject({ segment: z.string().min(1) })]);
179
153
  /**
180
- * A destination's name: the token a journey addresses it by, in a
181
- * `send.webhook` call or a journey's `senderIdentity`.
154
+ * The address half of a from-header: a local part, an `@`, and a dotted
155
+ * domain. Deliberately narrower than RFC 5322 (no quoted local parts, no
156
+ * address literals): every character it refuses would have to be quoted or
157
+ * escaped to survive a header, and none of them belong in an address a
158
+ * journey sends from.
159
+ */
160
+ const FROM_ADDRESS = /^[^\s@<>",;]+@([^\s@<>",;.]+(?:\.[^\s@<>",;.]+)+)$/;
161
+ /**
162
+ * Parse a journey's or a send's `from`: `addr@domain`, or
163
+ * `Name <addr@domain>` with the name optionally quoted. Null when it is
164
+ * neither, which is what `cow build` refuses on and what the send-time
165
+ * domain gate turns into a skip.
182
166
  *
183
- * Defined here rather than beside the destinations contract, and imported
184
- * from here by it, because a journey manifest names one and the guest layer
185
- * is bundled into every tenant module: importing it the other way round
186
- * would pull the Drizzle destinations table into all of them.
167
+ * One parser, and the send facade reuses it. A control character anywhere is
168
+ * a refusal rather than something to strip: it survives quoting and would
169
+ * inject a second header.
170
+ */
171
+ function parseFromAddress(from) {
172
+ const text = from.trim();
173
+ if (/\p{Cc}/u.test(text)) return null;
174
+ const angled = /^(.*?)\s*<([^<>]*)>$/.exec(text);
175
+ const address = (angled?.[2] ?? text).trim();
176
+ const domain = FROM_ADDRESS.exec(address)?.[1];
177
+ if (!domain) return null;
178
+ const name = (angled?.[1] ?? "").trim().replace(/^"(.*)"$/s, "$1").replace(/\\(.)/g, "$1").trim();
179
+ return {
180
+ ...name ? { name } : {},
181
+ address,
182
+ domain: domain.toLowerCase()
183
+ };
184
+ }
185
+ /**
186
+ * The address a journey or one send leaves as: a string, not the name of a
187
+ * configured row (ADR 0014). The domain is checked against the
188
+ * organization's verified ones at push time and again at send time; the
189
+ * shape is all that is checked here, because it is all a build can know.
187
190
  */
188
- const destinationNameSchema = z.string().trim().min(1, "name is required").max(100, "name must be at most 100 characters");
191
+ const fromSchema = z.string().trim().max(200, "from must be at most 200 characters").refine((value) => parseFromAddress(value) !== null, { message: "from must be an address (\"billing@acme.com\") or a name and address (\"Billing <billing@acme.com>\")" });
189
192
  /** A content address: `sha256:` plus the lowercase hex digest. */
190
193
  const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
191
194
  /**
@@ -221,13 +224,6 @@ const SPINE_ENTRY_NAMES = [
221
224
  const spineEntrySchema = z.object({
222
225
  name: z.enum(SPINE_ENTRY_NAMES),
223
226
  detail: z.string().max(200).optional(),
224
- /**
225
- * The sender identity a `send.email` call named for itself, overriding
226
- * the journey's own. Present only when the author wrote one on the call,
227
- * which is what lets the deploy warning and the journey detail page name
228
- * the override without re-reading the code.
229
- */
230
- senderIdentity: z.string().max(100).optional(),
231
227
  /** A `waitForEvent` timeout, as the author wrote it. */
232
228
  timeout: z.string().max(50).optional(),
233
229
  get steps() {
@@ -237,6 +233,13 @@ const spineEntrySchema = z.object({
237
233
  return z.array(spineEntrySchema).optional();
238
234
  }
239
235
  }).meta({ id: "JourneySpineEntry" });
236
+ /**
237
+ * One journey in a stored manifest. A plain (non-strict) object on purpose:
238
+ * a manifest pushed before the author's rollout gate went away still
239
+ * carries keys that no longer exist, and every stored manifest has to keep
240
+ * parsing for good (ADR 0011). Zod strips them, and the journey runs when
241
+ * its `enabled` flag says so, which is the one gate there is.
242
+ */
240
243
  const manifestJourneySchema = z.object({
241
244
  key: journeyKeySchema,
242
245
  /** The author's labels; the dashboard's only grouping. */
@@ -244,23 +247,17 @@ const manifestJourneySchema = z.object({
244
247
  trigger: triggerSchema,
245
248
  /**
246
249
  * A fixed purpose or one the project declares. Checked against the org's
247
- * declared set at deploy time, not here: a manifest is built and pushed
250
+ * declared set at push time, not here: a manifest is built and pushed
248
251
  * without ever reaching the org whose rows say what exists.
249
252
  */
250
253
  purpose: consentPurposeKeySchema,
251
254
  /**
252
- * The sender identity every `send.email` in this journey goes out as,
253
- * unless the call names its own. A name, never a `dst_` id: an org has one
254
- * row per environment, so an id would send in production and fail in
255
- * development, which is the one thing a journey must not do.
256
- *
257
- * Optional here and required at `defineJourney`, exactly like `purposes`: a
258
- * release pushed before the field existed carries none and its stored
259
- * manifest still parses. The author's build is where the error is useful.
255
+ * The address every `send.email` in this journey goes out as, unless the
256
+ * call names its own. Optional everywhere: an omitted one is the
257
+ * organization's shared fallback address, which is what makes day-one
258
+ * sending zero-config (ADR 0014).
260
259
  */
261
- senderIdentity: destinationNameSchema.optional(),
262
- /** The author's rollout gate: the journey is active only in these. */
263
- environments: environmentsSchema,
260
+ from: fromSchema.optional(),
264
261
  spine: z.array(spineEntrySchema),
265
262
  bundle: digestSchema
266
263
  });
@@ -292,27 +289,27 @@ function uniqueKeys(items, ctx, path) {
292
289
  }
293
290
  /**
294
291
  * What `cow build` writes to `.cow/build/manifest.json`, and the shape a
295
- * release row stores for good.
292
+ * push row stores for good, entry by entry, on the versions it creates.
296
293
  *
297
294
  * `protocol` is any positive integer rather than the current constant on
298
- * purpose: a release is immutable and an execution stays pinned to the one it
299
- * started on, so the day the protocol is bumped every stored release must
300
- * still parse, or the runner, the deploy, and every API response the client
301
- * validates all break at once for any org with history. The literal lives at
302
- * push time only (`createReleaseBodySchema`), which is where a stale CLI is
303
- * the developer's own fixable problem, and deploy plus the runner refuse a
304
- * release built for another protocol with a message naming both numbers.
295
+ * purpose: a version is immutable and an execution stays pinned to the one
296
+ * it started on, so the day the protocol is bumped every stored push must
297
+ * still parse, or the runner and every API response the client validates
298
+ * break at once for any org with history. The literal lives at push time
299
+ * only (`createPushBodySchema`), which is where a stale CLI is the
300
+ * developer's own fixable problem, and the runner refuses a version built
301
+ * for another protocol with a message naming both numbers.
305
302
  */
306
303
  const manifestSchema = z.object({
307
304
  protocol: z.number().int().positive(),
308
305
  /** The `@cowliss/cli` version the project was built with. */
309
306
  sdk: z.string().min(1),
310
- journeys: z.array(manifestJourneySchema).max(RELEASE_LIMITS.journeys),
311
- templates: z.array(manifestTemplateSchema).max(RELEASE_LIMITS.templates),
307
+ journeys: z.array(manifestJourneySchema).max(PUSH_LIMITS.journeys),
308
+ templates: z.array(manifestTemplateSchema).max(PUSH_LIMITS.templates),
312
309
  /**
313
310
  * The consent purposes this project declares, copied from `cow.json`.
314
- * Optional rather than defaulted: a release pushed before purposes
315
- * existed carries none, and its stored manifest still parses.
311
+ * Optional rather than defaulted: a push made before purposes existed
312
+ * carries none, and its stored manifest still parses.
316
313
  */
317
314
  purposes: purposesSchema.optional(),
318
315
  /** Digest of the gzipped source tarball. */
@@ -323,27 +320,12 @@ const manifestSchema = z.object({
323
320
  uniqueKeys(manifest.purposes ?? [], ctx, "purposes");
324
321
  });
325
322
  /**
326
- * The manifest as the release row stores it once compilation succeeded:
327
- * the pushed manifest plus the compiled module digest per key, and the
328
- * digest of the Javy engine plugin the toolchain that compiled them was
329
- * built from.
330
- *
331
- * Journeys and templates are keyed separately because they share a key
332
- * space: `welcome.ts` and `welcome.tsx` are one journey and the email it
333
- * sends in every example, and a flat map would let one overwrite the other.
334
- *
335
- * `plugin` is a toolchain record, not a linked artifact: modules are
336
- * statically linked, so the plugin bytes are inside each module. It says
337
- * which engine compiled the release, which is what a later bug report or a
338
- * reproducible rebuild needs.
339
- */
340
- const compiledManifestSchema = manifestSchema.safeExtend({
341
- modules: z.object({
342
- journeys: z.record(journeyKeySchema, digestSchema),
343
- templates: z.record(journeyKeySchema, digestSchema)
344
- }),
345
- plugin: digestSchema
346
- });
323
+ * One entry of a stored manifest: what a version is a snapshot of. Journeys
324
+ * and templates are kept apart because they share a key space (`welcome.ts`
325
+ * and `welcome.tsx` are one journey and the email it sends in every
326
+ * example), and the version's own `kind` says which of the two this is.
327
+ */
328
+ const versionManifestSchema = z.union([manifestJourneySchema, manifestTemplateSchema]);
347
329
 
348
330
  //#endregion
349
331
  //#region ../../packages/shared/src/journeys-v2/config.ts
@@ -355,9 +337,8 @@ const compiledManifestSchema = manifestSchema.safeExtend({
355
337
  const projectNameSchema = z.string().trim().min(1).max(200);
356
338
  /**
357
339
  * `cow.json` (spec: Project layout): the org, and which of its projects this
358
- * directory is. The environment is always a flag, and auth never lives in
359
- * the project. Strict, so a typo'd key is a build error rather than a
360
- * silently ignored setting.
340
+ * directory is. Auth never lives in the project. Strict, so a typo'd key
341
+ * is a build error rather than a silently ignored setting.
361
342
  */
362
343
  const cowConfigSchema = z.strictObject({
363
344
  $schema: z.url().optional(),
@@ -385,7 +366,7 @@ const cowConfigSchema = z.strictObject({
385
366
  webUrl: z.url().optional()
386
367
  }).meta({
387
368
  title: "cow.json",
388
- description: "A cow project: the organization and the project it deploys to."
369
+ description: "A cow project: the organization and the project it pushes to."
389
370
  });
390
371
 
391
372
  //#endregion
@@ -412,8 +393,8 @@ const properties = z.record(z.string(), z.unknown());
412
393
  * them. A name outside the union is rejected, never dispatched.
413
394
  *
414
395
  * Every `args` is strict, and that is the tenancy boundary made mechanical:
415
- * a module that returns an `orgId`, an `environment`, or any other field
416
- * beside the ones a capability takes fails the parse instead of having it
396
+ * a module that returns an `orgId` or any other field beside the ones a
397
+ * capability takes fails the parse instead of having it
417
398
  * quietly dropped. Tenant context comes from the workflow input, never from
418
399
  * guest output, and this is where saying so becomes checkable.
419
400
  */
@@ -435,12 +416,14 @@ const commandSchema = z.discriminatedUnion("name", [
435
416
  template: journeyKeySchema,
436
417
  props: properties,
437
418
  /**
438
- * Which sender identity this mail leaves as, by name. Required, and
439
- * the guest SDK fills in the journey's own when the call does not name
440
- * one, so the host has one resolution path and never has to read the
441
- * manifest to find a sender.
419
+ * The address this mail leaves as. The guest SDK fills in the
420
+ * journey's own whenever the call does not name one, so the host has
421
+ * one resolution path and never reads the manifest to find it;
422
+ * absent on both is the organization's shared fallback address.
442
423
  */
443
- senderIdentity: destinationNameSchema
424
+ from: fromSchema.optional(),
425
+ /** Where a reply to this one mail goes, instead of the from-address. */
426
+ replyTo: z.email().optional()
444
427
  })
445
428
  }),
446
429
  z.object({
@@ -521,7 +504,7 @@ const executionLimitsSchema = z.object({
521
504
  logLineBytes: z.number().int().positive()
522
505
  });
523
506
  const journeyStepInputSchema = z.object({
524
- protocol: z.literal(2),
507
+ protocol: z.literal(3),
525
508
  kind: z.literal("journey"),
526
509
  key: journeyKeySchema,
527
510
  event: guestEventSchema,
@@ -561,7 +544,7 @@ const journeyStepOutputSchema = z.discriminatedUnion("status", [
561
544
  })
562
545
  ]);
563
546
  const templateRenderInputSchema = z.object({
564
- protocol: z.literal(2),
547
+ protocol: z.literal(3),
565
548
  kind: z.literal("template"),
566
549
  key: journeyKeySchema,
567
550
  props: properties
@@ -580,8 +563,7 @@ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
580
563
  const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
581
564
  trigger: true,
582
565
  purpose: true,
583
- senderIdentity: true,
584
- environments: true,
566
+ from: true,
585
567
  tags: true
586
568
  }).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
587
569
  sendClass: true,
@@ -637,7 +619,7 @@ const sandboxFailureCodeSchema = z.enum(SANDBOX_FAILURE_CODES);
637
619
  * to be written by hand or by an agent, not generated.
638
620
  *
639
621
  * The schema lives here rather than in packages/journeys so the CLI can reject
640
- * a bad file before it boots a Temporal environment, and so the docs site can
622
+ * a bad file before it boots a Temporal dev server, and so the docs site can
641
623
  * render the format from one source.
642
624
  */
643
625
  const journeyScenarioSchema = z.object({
@@ -679,7 +661,7 @@ const journeyScenarioSchema = z.object({
679
661
  //#endregion
680
662
  //#region src/guest/journeys.ts
681
663
  /**
682
- * A capability that failed host-side (an unknown destination, invalid
664
+ * A capability that failed host-side (an unknown webhook, invalid
683
665
  * props, a profile that is not there). The failure is journaled, so a
684
666
  * journey that catches it takes the same branch on every replay.
685
667
  */
@@ -693,25 +675,29 @@ var CapabilityError = class extends Error {
693
675
  };
694
676
  /**
695
677
  * The config half of a journey, validated with the same schema the release
696
- * manifest is validated with, so a bad trigger or a malformed purpose key
697
- * fails at build time rather than at the first execution. Whether the org
698
- * declares that purpose is a question only the deploy can answer.
678
+ * manifest is validated with, so a bad trigger, a malformed purpose key, or
679
+ * a `from` that is not an address fails at build time rather than at the
680
+ * first execution. Whether the org declares that purpose, and whether it may
681
+ * send from that domain, are questions only the push can answer.
699
682
  */
700
683
  const journeyConfigSchema = manifestJourneySchema.pick({
701
684
  trigger: true,
702
685
  purpose: true,
703
- senderIdentity: true,
704
- environments: true,
686
+ from: true,
705
687
  tags: true
706
- }).extend({ senderIdentity: destinationNameSchema });
707
- /** Author a journey. Throws at definition time on an invalid config. */
688
+ });
689
+ /**
690
+ * Author a journey. Throws at definition time on an invalid config.
691
+ *
692
+ * There is no environment gate here: an organization has one data space, and
693
+ * a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
694
+ */
708
695
  function defineJourney(input) {
709
696
  return {
710
697
  ...journeyConfigSchema.parse({
711
698
  trigger: input.trigger,
712
699
  purpose: input.purpose,
713
- senderIdentity: input.senderIdentity,
714
- environments: input.environments ?? [...ENVIRONMENTS],
700
+ from: input.from,
715
701
  tags: input.tags
716
702
  }),
717
703
  run: input.run
@@ -1,12 +1,11 @@
1
- import { n as Duration, r as GuestEvent, t as Trigger } from "./index-DAGLEHDn.js";
2
- import { t as Environment } from "./constants-B0wk-87t.js";
1
+ import { n as Duration, r as GuestEvent, t as Trigger } from "./index-CXcCEcAg.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;
6
5
  /**
7
6
  * A profile as the capability layer hands it back: the recipient of this
8
- * execution (`api.profile.get()`) or another profile in the same org and
9
- * environment (`api.profiles.get(id)`). A fresh read on every call.
7
+ * execution (`api.profile.get()`) or another profile in the same org
8
+ * (`api.profiles.get(id)`). A fresh read on every call.
10
9
  */
11
10
  type Profile = {
12
11
  /** The Cowliss-generated profile id (`usr_`). */
@@ -24,7 +23,7 @@ type EmailSendResult = {
24
23
  status: string;
25
24
  };
26
25
  /**
27
- * A capability that failed host-side (an unknown destination, invalid
26
+ * A capability that failed host-side (an unknown webhook, invalid
28
27
  * props, a profile that is not there). The failure is journaled, so a
29
28
  * journey that catches it takes the same branch on every replay.
30
29
  */
@@ -69,11 +68,16 @@ type Api = {
69
68
  template: Key;
70
69
  props: TemplateProps<Key>;
71
70
  /**
72
- * Send this one mail from a different sender identity than the
73
- * journey's own. A name the organization has configured, resolved in
74
- * the environment the execution is running in.
71
+ * Send this one mail from a different address than the journey's own:
72
+ * `"billing@acme.com"` or `"Billing <billing@acme.com>"`. The domain
73
+ * must be one your organization has verified, or the send is skipped.
75
74
  */
76
- senderIdentity?: string;
75
+ from?: string;
76
+ /**
77
+ * Where a reply to this one mail goes, instead of the from-address.
78
+ * Any address: nothing is sent from it.
79
+ */
80
+ replyTo?: string;
77
81
  }): Promise<EmailSendResult>;
78
82
  webhook(args: {
79
83
  destination: string;
@@ -123,27 +127,29 @@ type JourneyConfig = {
123
127
  */
124
128
  purpose: string;
125
129
  /**
126
- * The sender identity every `api.send.email` in this journey goes out as:
127
- * the name of one your organization has configured, or `cowliss-default`
128
- * for the address Cowliss gives you with nothing to set up. A single send
130
+ * The address every `api.send.email` in this journey goes out as:
131
+ * `"billing@acme.com"` or `"Billing <billing@acme.com>"`. A single send
129
132
  * can override it.
130
133
  *
131
- * Required, and named here rather than in a settings page, so the address
132
- * a journey sends from is readable in the journey's own source. It is
133
- * resolved per environment, which is why it is a name and not an id.
134
+ * Optional: leave it out and Cowliss sends from the address it gives your
135
+ * organization, with nothing to set up. Named here rather than in a
136
+ * settings page, so the address a journey sends from is readable in the
137
+ * journey's own source.
134
138
  */
135
- senderIdentity: string;
136
- /** The author's rollout gate; defaults to every environment. */
137
- environments: Environment[];
139
+ from?: string;
138
140
  /** The author's labels; the dashboard's only grouping. Defaults to none. */
139
141
  tags: string[];
140
142
  };
141
143
  type Journey = JourneyConfig & {
142
144
  run: (event: Event, api: Api) => Promise<void>;
143
145
  };
144
- /** Author a journey. Throws at definition time on an invalid config. */
145
- declare function defineJourney(input: Omit<JourneyConfig, "environments" | "tags"> & {
146
- environments?: Environment[];
146
+ /**
147
+ * Author a journey. Throws at definition time on an invalid config.
148
+ *
149
+ * There is no environment gate here: an organization has one data space, and
150
+ * a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
151
+ */
152
+ declare function defineJourney(input: Omit<JourneyConfig, "tags"> & {
147
153
  tags?: string[];
148
154
  run: (event: Event, api: Api) => Promise<void>;
149
155
  }): Journey;
@@ -1,3 +1,3 @@
1
- import { n as defineJourney, t as CapabilityError } from "./journeys-B_xg_GnL.js";
1
+ import { n as defineJourney, t as CapabilityError } from "./journeys-BrcKEXz0.js";
2
2
 
3
3
  export { CapabilityError, defineJourney };
@@ -1,4 +1,4 @@
1
- import { t as runGuest } from "./driver-DaNdNhuF.js";
1
+ import { t as runGuest } from "./driver-DBt5l1Z5.js";
2
2
 
3
3
  //#region src/guest/wasi.ts
4
4
  const STDIN = 0;