@sanoma-ai/workflows 0.5.0 → 0.7.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.
Files changed (153) hide show
  1. package/README.md +49 -15
  2. package/dist/apply.d.ts +2 -1
  3. package/dist/apply.js +29 -7
  4. package/dist/apply.js.map +1 -1
  5. package/dist/approvals.d.ts +27 -2
  6. package/dist/approvals.js +73 -9
  7. package/dist/approvals.js.map +1 -1
  8. package/dist/call.d.ts +3 -2
  9. package/dist/call.js +38 -30
  10. package/dist/call.js.map +1 -1
  11. package/dist/cedar.js +24 -9
  12. package/dist/cedar.js.map +1 -1
  13. package/dist/change.d.ts +219 -0
  14. package/dist/change.js +150 -0
  15. package/dist/change.js.map +1 -0
  16. package/dist/chats.d.ts +43 -0
  17. package/dist/chats.js +48 -0
  18. package/dist/chats.js.map +1 -0
  19. package/dist/client.d.ts +52 -6
  20. package/dist/client.js +88 -47
  21. package/dist/client.js.map +1 -1
  22. package/dist/config.d.ts +47 -5
  23. package/dist/config.js +71 -7
  24. package/dist/config.js.map +1 -1
  25. package/dist/database.d.ts +83 -0
  26. package/dist/database.js +92 -0
  27. package/dist/database.js.map +1 -0
  28. package/dist/db.d.ts +31 -1
  29. package/dist/db.js +89 -7
  30. package/dist/db.js.map +1 -1
  31. package/dist/deadlines.d.ts +88 -0
  32. package/dist/deadlines.js +171 -0
  33. package/dist/deadlines.js.map +1 -0
  34. package/dist/define.d.ts +25 -4
  35. package/dist/define.js +40 -0
  36. package/dist/define.js.map +1 -1
  37. package/dist/demo.d.ts +28 -0
  38. package/dist/demo.js +72 -0
  39. package/dist/demo.js.map +1 -0
  40. package/dist/describe.d.ts +6 -3
  41. package/dist/describe.js +11 -7
  42. package/dist/describe.js.map +1 -1
  43. package/dist/errors.d.ts +10 -1
  44. package/dist/errors.js +13 -0
  45. package/dist/errors.js.map +1 -1
  46. package/dist/fake.d.ts +8 -0
  47. package/dist/fake.js +11 -1
  48. package/dist/fake.js.map +1 -1
  49. package/dist/grid.d.ts +34 -0
  50. package/dist/grid.js +62 -0
  51. package/dist/grid.js.map +1 -0
  52. package/dist/host.d.ts +13 -2
  53. package/dist/host.js +2 -2
  54. package/dist/host.js.map +1 -1
  55. package/dist/index.d.ts +16 -8
  56. package/dist/index.js +12 -5
  57. package/dist/index.js.map +1 -1
  58. package/dist/ledger.d.ts +11 -2
  59. package/dist/ledger.js.map +1 -1
  60. package/dist/lint.js +1 -1
  61. package/dist/lint.js.map +1 -1
  62. package/dist/op.d.ts +14 -2
  63. package/dist/op.js.map +1 -1
  64. package/dist/policy.d.ts +7 -1
  65. package/dist/policy.js +2 -1
  66. package/dist/policy.js.map +1 -1
  67. package/dist/resource.d.ts +13 -1
  68. package/dist/resource.js +9 -1
  69. package/dist/resource.js.map +1 -1
  70. package/dist/resources.d.ts +2 -0
  71. package/dist/resources.js +10 -1
  72. package/dist/resources.js.map +1 -1
  73. package/dist/run.d.ts +5 -0
  74. package/dist/run.js.map +1 -1
  75. package/dist/sandbox.d.ts +38 -10
  76. package/dist/sandbox.js +66 -35
  77. package/dist/sandbox.js.map +1 -1
  78. package/dist/scenario.d.ts +18 -1
  79. package/dist/scenario.js +77 -27
  80. package/dist/scenario.js.map +1 -1
  81. package/dist/scenarios.d.ts +10 -1
  82. package/dist/scenarios.js +19 -4
  83. package/dist/scenarios.js.map +1 -1
  84. package/dist/schedules.d.ts +66 -0
  85. package/dist/schedules.js +158 -0
  86. package/dist/schedules.js.map +1 -0
  87. package/dist/schema.d.ts +6 -0
  88. package/dist/schema.js +13 -3
  89. package/dist/schema.js.map +1 -1
  90. package/dist/shared.d.ts +38 -2
  91. package/dist/shared.js +50 -4
  92. package/dist/shared.js.map +1 -1
  93. package/dist/sites.d.ts +38 -1
  94. package/dist/sites.js +153 -26
  95. package/dist/sites.js.map +1 -1
  96. package/dist/source.d.ts +61 -2
  97. package/dist/source.js.map +1 -1
  98. package/dist/supervisor.d.ts +38 -0
  99. package/dist/supervisor.js +77 -0
  100. package/dist/supervisor.js.map +1 -0
  101. package/dist/version.d.ts +3 -1
  102. package/dist/version.js +3 -1
  103. package/dist/version.js.map +1 -1
  104. package/dist/wakeup-service.d.ts +83 -0
  105. package/dist/wakeup-service.js +421 -0
  106. package/dist/wakeup-service.js.map +1 -0
  107. package/dist/wakeup.d.ts +84 -0
  108. package/dist/wakeup.js +87 -0
  109. package/dist/wakeup.js.map +1 -0
  110. package/dist/worker.d.ts +39 -1
  111. package/dist/worker.js +130 -39
  112. package/dist/worker.js.map +1 -1
  113. package/oxlint.json +2 -1
  114. package/package.json +8 -2
  115. package/src/apply.ts +25 -6
  116. package/src/approvals.ts +89 -16
  117. package/src/call.ts +40 -29
  118. package/src/cedar.ts +24 -6
  119. package/src/change.ts +222 -0
  120. package/src/chats.ts +114 -0
  121. package/src/client.ts +145 -55
  122. package/src/config.ts +110 -13
  123. package/src/database.ts +152 -0
  124. package/src/db.ts +108 -9
  125. package/src/deadlines.ts +234 -0
  126. package/src/define.ts +71 -4
  127. package/src/demo.ts +87 -0
  128. package/src/describe.ts +19 -7
  129. package/src/errors.ts +14 -0
  130. package/src/fake.ts +12 -0
  131. package/src/grid.ts +100 -0
  132. package/src/host.ts +13 -4
  133. package/src/index.ts +62 -3
  134. package/src/ledger.ts +15 -3
  135. package/src/lint.ts +1 -1
  136. package/src/op.ts +15 -2
  137. package/src/policy.ts +7 -1
  138. package/src/resource.ts +22 -3
  139. package/src/resources.ts +12 -1
  140. package/src/run.ts +5 -0
  141. package/src/sandbox.ts +97 -37
  142. package/src/scenario.ts +97 -28
  143. package/src/scenarios.ts +21 -5
  144. package/src/schedules.ts +206 -0
  145. package/src/schema.ts +16 -3
  146. package/src/shared.ts +62 -4
  147. package/src/sites.ts +198 -33
  148. package/src/source.ts +58 -3
  149. package/src/supervisor.ts +107 -0
  150. package/src/version.ts +3 -1
  151. package/src/wakeup-service.ts +511 -0
  152. package/src/wakeup.ts +150 -0
  153. package/src/worker.ts +181 -44
package/dist/sandbox.js CHANGED
@@ -53,16 +53,54 @@ export function seedResource(scenario, copy, given) {
53
53
  throw new SanomaError("invalid_input", `Scenario "${scenario}" cannot seed ${resource} "${name}" ("${given.step}"): ${errorMessage(err)}`, { scenario, step: given.step, resource, name }, { cause: err });
54
54
  }
55
55
  }
56
+ /**
57
+ * Applies the scenario's `Given` steps to the target: calls each operation through its drivers,
58
+ * puts or removes each resource in its vendor's fake, then, for a `run`, injects the faults.
59
+ * Checks first that every operation it seeds has a driver there, that every resource's vendor has
60
+ * a fake holding resources, and, for an `instance`, that it injects no fault. Returns what it
61
+ * seeded, in order.
62
+ */
63
+ export async function applyGivens(scenario, target) {
64
+ const { name, given } = scenario;
65
+ const fault = given.find((g) => "fault" in g);
66
+ if (fault && target.kind === "instance") {
67
+ throw new SanomaError("invalid_input", `Scenario "${name}" injects a fault ("${fault.step}"), but its Givens go to the instance, whose fakes every run shares`, { scenario: name, step: fault.step });
68
+ }
69
+ const missing = [...new Set(given.flatMap((g) => ("op" in g && !target.drivers.has(g.op) ? [g.op] : [])))];
70
+ if (missing.length) {
71
+ throw new SanomaError("invalid_input", `Scenario "${name}" seeds ${missing.join(", ")}, but no fake its connectors attach implements ${missing.length > 1 ? "them" : "it"}`, { scenario: name, missing });
72
+ }
73
+ const plain = missingResourceFake(scenario, target.fakes);
74
+ if (plain)
75
+ throw plain;
76
+ const seeded = [];
77
+ for (const [i, g] of given.entries()) {
78
+ if ("fault" in g)
79
+ continue;
80
+ if ("op" in g) {
81
+ const output = await target.drivers.get(g.op)(g.input, target.call(i, g.op));
82
+ seeded.push({ op: g.op, input: g.input, output });
83
+ continue;
84
+ }
85
+ // Checked above: the vendor's fake holds resources.
86
+ seeded.push(seedResource(name, target.fakes.get(g.vendor), g));
87
+ }
88
+ for (const g of given) {
89
+ if ("fault" in g)
90
+ target.fakes.get(target.ops.get(g.op).vendor)[g.fault](g.op);
91
+ }
92
+ return seeded;
93
+ }
56
94
  /**
57
95
  * Seeds a sandbox run of the scenario `name`, from the worker, in one step: reads the scenario,
58
- * checks that it runs this workflow, makes the run's own fakes (each vendor's maker, called once
59
- * for the run), checks that every operation the workflow uses or the scenario seeds has one and
60
- * that every resource it seeds has a fake holding resources, calls each `Given` operation
61
- * through them, puts or removes each `Given` resource in them, injects the faults into them,
62
- * then puts their functions on the run. The run then calls those only, so sandbox runs share no
63
- * state when the makers make a new fake each. The step's checkpoint never stands in for them: a
64
- * run whose seeding is replayed from it has none, and fails, since a sandbox run does not
65
- * survive a worker restart.
96
+ * checks that it runs this workflow, picks the run's fakes, checks that every operation the
97
+ * workflow uses has one, then applies the scenario's `Given` steps to them
98
+ * (`applyGivens`) and puts their functions on the run. The run then calls those only. Its fakes
99
+ * are its own (each vendor's maker, called once for the run), so sandbox runs share no state when
100
+ * the makers make a new fake each; on an instance that runs on fakes (`instanceFakes`) they are
101
+ * the instance's, which its sandbox runs share, as its live drivers are. The step's checkpoint
102
+ * never stands in for a run's own: a run whose seeding is replayed from it has none, and fails,
103
+ * since a sandbox run does not survive a worker restart; on an instance's fakes it goes on with them.
66
104
  */
67
105
  export async function seedSandbox(run, name) {
68
106
  const { state } = run;
@@ -78,42 +116,35 @@ export async function seedSandbox(run, name) {
78
116
  throw new SanomaError("invalid_input", `No scenario named "${name}"${why}`, { scenario: name });
79
117
  }
80
118
  if (scenario.workflow !== run.workflow) {
81
- throw new SanomaError("invalid_input", `Scenario "${name}" runs ${scenario.workflow}, not ${run.workflow}`, {
119
+ const runs = scenario.workflow === undefined ? "is a world, which runs nothing" : `runs ${scenario.workflow}`;
120
+ throw new SanomaError("invalid_input", `Scenario "${name}" ${runs}, not ${run.workflow}`, {
82
121
  scenario: name,
83
- workflow: scenario.workflow,
122
+ ...(scenario.workflow !== undefined && { workflow: scenario.workflow }),
84
123
  });
85
124
  }
86
- const fakes = new Map([...state.fakes].map(([vendor, make]) => [vendor, make()]));
87
- const drivers = indexDrivers([...fakes.values()].map((f) => f.driver), state.ops);
88
- const uses = opIdsOf(state.workflows.get(run.workflow));
89
- const seeding = scenario.given.flatMap((g) => ("op" in g ? [g.op] : []));
90
- const missing = [...new Set([...uses, ...seeding])].filter((id) => !drivers.has(id));
125
+ const instance = state.instanceFakes;
126
+ const fakes = instance ?? new Map([...state.fakes].map(([vendor, make]) => [vendor, make()]));
127
+ const drivers = instance
128
+ ? state.drivers
129
+ : indexDrivers([...fakes.values()].map((f) => f.driver), state.ops);
130
+ // What the scenario seeds is checked by `applyGivens`.
131
+ const missing = opIdsOf(state.workflows.get(run.workflow)).filter((id) => !drivers.has(id));
91
132
  if (missing.length) {
92
133
  throw new SanomaError("invalid_input", `A sandbox run of ${run.workflow} has no fake for ${missing.join(", ")}: no fake its connectors attach implements ${missing.length > 1 ? "them" : "it"}`, { scenario: name, missing });
93
134
  }
94
- const plain = missingResourceFake(scenario, fakes);
95
- if (plain)
96
- throw plain;
97
- const seeded = [];
98
- for (const [i, given] of scenario.given.entries()) {
99
- if ("fault" in given)
100
- continue;
101
- if ("op" in given) {
102
- const call = { idempotencyKey: `${run.id}:seed:${i}`, runId: run.id, opId: given.op, attempt: 1, secrets };
103
- const output = await drivers.get(given.op)(given.input, call);
104
- seeded.push({ op: given.op, input: given.input, output });
105
- continue;
106
- }
107
- // Checked above: the vendor's fake holds resources.
108
- seeded.push(seedResource(name, fakes.get(given.vendor), given));
109
- }
110
- for (const given of scenario.given) {
111
- if ("fault" in given)
112
- fakes.get(state.ops.get(given.op).vendor)[given.fault](given.op);
113
- }
135
+ const seeded = await applyGivens(scenario, {
136
+ kind: instance ? "instance" : "run",
137
+ drivers,
138
+ fakes,
139
+ ops: state.ops,
140
+ call: (i, opId) => ({ idempotencyKey: `${run.id}:seed:${i}`, runId: run.id, opId, attempt: 1, secrets }),
141
+ });
114
142
  copies = drivers;
115
143
  return { seeds: seeded };
116
144
  }, { name: "sandbox:seed" });
145
+ // Replayed on an instance that runs on fakes: they are the instance's, still there in this process.
146
+ if (!copies && state.instanceFakes)
147
+ copies = state.drivers;
117
148
  if (!copies) {
118
149
  // Replayed from its checkpoint (after a worker restart, or in a fork), so the run has no fakes.
119
150
  // The records written before the restart hold the seqs from here on: the failure goes after them.
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
@@ -129,7 +129,19 @@ export interface Scenario {
129
129
  file: string;
130
130
  /** Its line in the file: its keyword's, or for an outline's row, the row's. */
131
131
  line: number;
132
- workflow: string;
132
+ /**
133
+ * The workflow its `When` runs. Absent for a scenario with no `When`, a *world*: its `Given`
134
+ * steps alone, which a demo applies to the instance (`applyGivens`) and nothing runs or checks.
135
+ */
136
+ workflow?: string;
137
+ /** Its tags, as written (`@slow`), the feature's and its own. */
138
+ tags: string[];
139
+ /**
140
+ * When it is tagged `@moment(<person>,<path>)` (no space: a Gherkin tag has none): a moment,
141
+ * which a demo starts as a sandbox run by `person`, a declared person, and leaves waiting, for
142
+ * a visitor signed in as them to land on at `path`.
143
+ */
144
+ moment?: Moment;
133
145
  /**
134
146
  * The scenario's own lines of its feature file, for display: from its keyword line to the end
135
147
  * of its last step (a step's doc string or table included, and an outline's examples), less
@@ -151,6 +163,11 @@ export interface Scenario {
151
163
  approvals: string[];
152
164
  expect: Expectation[];
153
165
  }
166
+ /** A moment's visitor and where they land: `@moment(<person>,<path>)`. */
167
+ export interface Moment {
168
+ person: string;
169
+ path: string;
170
+ }
154
171
  /** The result of one expectation against a run's ledger. */
155
172
  export interface Check {
156
173
  step: string;
package/dist/scenario.js CHANGED
@@ -150,6 +150,23 @@ function builtFor(scope) {
150
150
  /** The built-in workflows, whose input is never made up: each field is optional, and left out unless given. */
151
151
  /** A `Then` step of the generic steps: its expectation, checked against the run's records. */
152
152
  const expecting = (expect) => ({ expect, verify: (run) => detailOf(expect, run.records) });
153
+ /**
154
+ * `who`, as a run's start admits them (`admit`): a declared person or agent, or `anonymous`. The
155
+ * people are the root's data files' (`readPeople`, read again only once a file changed): while
156
+ * they declare no one, any id goes.
157
+ */
158
+ function actorNamed(scope, who) {
159
+ const declared = readPeople(scope.root);
160
+ try {
161
+ return admit({ id: who }, declared).id;
162
+ }
163
+ catch (err) {
164
+ if (errorCode(err) !== "not_admitted" || declared.problems.length)
165
+ throw err;
166
+ const ids = [...principalIdsOf(declared.people), ...RUNTIME_AGENTS].join(", ");
167
+ throw new Error(`${who} is not a declared person or agent; resources/ declares ${ids}`, { cause: err });
168
+ }
169
+ }
153
170
  /** The rules a scope has, and the parameter types their expressions use. */
154
171
  function rulesOf(scope) {
155
172
  const { types, collections } = scope;
@@ -172,22 +189,10 @@ function rulesOf(scope) {
172
189
  ];
173
190
  const opOf = (id) => scope.ops.get(id);
174
191
  const workflowOf = (name) => scope.workflows.get(name);
175
- // The people as the root's data files declare them when a step names one (`readPeople`, read
176
- // again only once a file changed): while they declare no one, any id and any group go.
177
- /** `who`, as a run's start admits them (`admit`): a declared person or agent, or `anonymous`. */
178
- const actorNamed = (who) => {
179
- const declared = readPeople(scope.root);
180
- try {
181
- return admit({ id: who }, declared).id;
182
- }
183
- catch (err) {
184
- if (errorCode(err) !== "not_admitted" || declared.problems.length)
185
- throw err;
186
- const ids = [...principalIdsOf(declared.people), ...RUNTIME_AGENTS].join(", ");
187
- throw new Error(`${who} is not a declared person or agent; resources/ declares ${ids}`, { cause: err });
188
- }
189
- };
190
- /** `group`, a declared group or `agents`, while the people's files have no problems. */
192
+ /**
193
+ * `group`, a declared group or `agents`, while the people's files have no problems (`readPeople`,
194
+ * read again only once a file changed): while they declare no one, any group goes.
195
+ */
191
196
  const groupNamed = (group) => {
192
197
  const { people, problems } = readPeople(scope.root);
193
198
  if (problems.length) {
@@ -204,7 +209,7 @@ function rulesOf(scope) {
204
209
  return { resource: id, vendor, type, name: name };
205
210
  };
206
211
  const rules = [
207
- { kind: "actor", source: "{who} is acting", read: ([who]) => actorNamed(who) },
212
+ { kind: "actor", source: "{who} is acting", read: ([who]) => actorNamed(scope, who) },
208
213
  // A visitor of a public site, as a login would vouch for one; `anonymous` is a `{who}`.
209
214
  { kind: "actor", source: "a visitor is acting", read: () => visitorPrincipal("scenario", "visitor").id },
210
215
  {
@@ -903,6 +908,29 @@ function stepError(built, result, step, where) {
903
908
  return `${where}: ${result.message ?? `the step ended ${result.status}`}`;
904
909
  }
905
910
  }
911
+ /**
912
+ * The moment its tags make a scenario (`@moment(<person>,<path>)`), the person admitted as a run's
913
+ * starter is, or undefined for none; throws, at `where`, for a malformed one or a second.
914
+ */
915
+ function momentOf(scope, tags, where) {
916
+ const tagged = tags.filter((t) => t === "@moment" || t.startsWith("@moment("));
917
+ if (tagged.length === 0)
918
+ return undefined;
919
+ if (tagged.length > 1)
920
+ throw new Error(`${where}: ${tagged.join(" and ")}: a moment has one person and one page`);
921
+ const [, person, path] = /^@moment\(([^,()]+),(\/[^()]*)\)$/.exec(tagged[0]) ?? [];
922
+ if (person === undefined || path === undefined) {
923
+ throw new Error(`${where}: ${tagged[0]} needs a person and a path, as in @moment(alex,/sites/portal/requests), with no space: a tag has none`);
924
+ }
925
+ try {
926
+ return { person: actorNamed(scope, person), path };
927
+ }
928
+ catch (err) {
929
+ if (isBug(err))
930
+ throw err;
931
+ throw new Error(`${where}: ${tagged[0]}: ${errorMessage(err)}`, { cause: err });
932
+ }
933
+ }
906
934
  /** The `.feature` files under `dir`, relative to it, with `/` between directories, in order. */
907
935
  function featureFiles(dir) {
908
936
  return readdirSync(dir, { recursive: true, encoding: "utf8" })
@@ -914,8 +942,9 @@ function featureFiles(dir) {
914
942
  const parseErrorOf = ({ uri, line, message }) => `${uri}${line === undefined ? "" : `:${line}`}: ${message}`;
915
943
  /**
916
944
  * Compiles the feature files `files` (relative to `dir`) into scenarios: Cucumber runs each,
917
- * and its steps record what they say. A file with an error has none of its scenarios: the
918
- * first error in it (a step that fails or matches no step or two, a scenario with no `When`,
945
+ * and its steps record what they say. A file none of whose scenarios has a `When` is of worlds.
946
+ * A file with an error has none of its scenarios: the
947
+ * first error in it (a step that fails or matches no step or two, a scenario with no `When` beside one with,
919
948
  * a name used twice, a file Gherkin cannot parse, no scenarios at all) is its error.
920
949
  */
921
950
  async function compile(scope, dir, files) {
@@ -956,6 +985,8 @@ async function compile(scope, dir, files) {
956
985
  const list = pickles.get(file) ?? [];
957
986
  if (list.length === 0)
958
987
  throw new Error(`${file}: no scenarios; add one with "Scenario: <name>"`);
988
+ // A feature none of whose scenarios has a When is of worlds; elsewhere a missing When is a mistake.
989
+ const ofWorlds = list.every((p) => specs.get(p.id).workflow === undefined);
959
990
  for (const pickle of list) {
960
991
  const line = lineOf(report, pickle);
961
992
  const scenario = query.findLineageBy(pickle).scenario;
@@ -965,13 +996,32 @@ async function compile(scope, dir, files) {
965
996
  if (error)
966
997
  throw new Error(error);
967
998
  }
968
- const { workflow, ...spec } = specs.get(pickle.id);
999
+ const spec = specs.get(pickle.id);
1000
+ const { workflow } = spec;
969
1001
  const name = names.get(pickle.id);
970
1002
  if (inFile.some((s) => s.name === name)) {
971
1003
  throw new Error(`${where}: a second scenario named "${name}"; give each its own name`);
972
1004
  }
1005
+ const tags = pickle.tags.map((t) => t.name);
1006
+ const moment = momentOf(scope, tags, where);
1007
+ const common = { name, file, line, tags, text: textOf(scenario, report.texts.get(file) ?? "") };
1008
+ if (workflow === undefined && !ofWorlds) {
1009
+ throw new Error(`${where}: scenario "${name}" has no When: say which workflow runs, such as "When <workflow> runs with" (a feature with no When in any scenario is of worlds)`);
1010
+ }
973
1011
  if (workflow === undefined) {
974
- throw new Error(`${where}: scenario "${name}" has no When: say which workflow runs, such as "When <workflow> runs with"`);
1012
+ // A world: its Givens alone, which nothing runs or checks.
1013
+ const runs = moment
1014
+ ? "is a moment, which starts a run"
1015
+ : spec.expect[0]
1016
+ ? `checks a run ("${spec.expect[0].step}")`
1017
+ : spec.decisions[0]
1018
+ ? `decides an approval ("${spec.decisions[0].step}")`
1019
+ : undefined;
1020
+ if (runs) {
1021
+ throw new Error(`${where}: scenario "${name}" has no When, so it is a world, Givens only, but it ${runs}: say which workflow runs, such as "When <workflow> runs with"`);
1022
+ }
1023
+ inFile.push({ ...spec, ...common, approvals: [] });
1024
+ continue;
975
1025
  }
976
1026
  // A step about a declared resource reads the report of the built-in it is about.
977
1027
  for (const e of spec.expect) {
@@ -982,11 +1032,9 @@ async function compile(scope, dir, files) {
982
1032
  }
983
1033
  inFile.push({
984
1034
  ...spec,
985
- name,
986
- file,
987
- line,
1035
+ ...common,
988
1036
  workflow,
989
- text: textOf(scenario, report.texts.get(file) ?? ""),
1037
+ ...(moment && { moment }),
990
1038
  approvals: approvalsOf(scope.workflows.get(workflow)),
991
1039
  });
992
1040
  }
@@ -1078,10 +1126,12 @@ export async function loadScenarios(scope) {
1078
1126
  export async function check(scope, scenario, records) {
1079
1127
  if (!scope.scenarios)
1080
1128
  throw new Error(`No scenarios directory to check scenario "${scenario.name}" from`);
1129
+ const { file, line, name, workflow } = scenario;
1130
+ if (workflow === undefined)
1131
+ throw new Error(`Scenario "${name}" is a world: it runs nothing to check`);
1081
1132
  const built = builtFor(scope);
1082
- const { file, line, name } = scenario;
1083
1133
  const verdicts = new Map();
1084
- const report = await runFeatures(built.library, checkWorld(built, scenario.workflow, viewOf(records), verdicts), {
1134
+ const report = await runFeatures(built.library, checkWorld(built, workflow, viewOf(records), verdicts), {
1085
1135
  cwd: fileURLToPath(scope.scenarios),
1086
1136
  files: [file],
1087
1137
  line,