@enyo-energy/energy-app-sdk 0.0.192 → 0.0.193

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.
@@ -133,6 +133,52 @@ exports.onboardingV2Block = {
133
133
  outcomes,
134
134
  deviceSelection,
135
135
  }),
136
+ /**
137
+ * An OCPP-connect action block: wait for the charger to dial into enyo's
138
+ * CSMS after the installer has entered the dynamic OCPP URL in it.
139
+ *
140
+ * A convenience wrapper over {@link onboardingV2Block.action} that pins the
141
+ * action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
142
+ * {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
143
+ * with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
144
+ * preceding step.
145
+ *
146
+ * Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
147
+ * and both must be wired — the validator enforces that, since a charger that
148
+ * never calls home is the common case.
149
+ *
150
+ * @param id - Stable block id, unique within the guide.
151
+ * @param label - Translated trigger button text (de/en).
152
+ * @param outcomes - The `connected` / `timeout` results; each is a routing handle.
153
+ */
154
+ ocppConnect: (id, label, outcomes) => ({
155
+ id,
156
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action,
157
+ action: enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect,
158
+ label,
159
+ outcomes,
160
+ }),
161
+ /**
162
+ * An auth block: the installer signs into the energy app's own account
163
+ * system (OAuth / vendor portal).
164
+ *
165
+ * Exactly one routing handle, and it means "the login succeeded". The server
166
+ * decides when it fires, so the installer cannot skip it; there is no failure
167
+ * branch to author — a failed attempt simply keeps them on the step. Route
168
+ * the handle with {@link onOutcomeV2}, passing `outcome.id`.
169
+ *
170
+ * @param id - Stable block id, unique within the guide.
171
+ * @param label - Translated sign-in button text (de/en).
172
+ * @param outcome - The single success handle (`{id, label}`).
173
+ * @param opts - Optional translated `help` naming the account that is needed.
174
+ */
175
+ auth: (id, label, outcome, opts) => ({
176
+ id,
177
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth,
178
+ label,
179
+ outcome,
180
+ help: opts?.help,
181
+ }),
136
182
  /**
137
183
  * A link block: a fixed URL the installer opens or copies.
138
184
  *
@@ -191,14 +237,40 @@ exports.onboardingV2Target = {
191
237
  step: (stepId) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step, stepId }),
192
238
  /** Exit: onboarding succeeded (hand back to the app). */
193
239
  success: () => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success }),
194
- /** Exit: escalate to enyo support. */
195
- support: () => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Support }),
240
+ /**
241
+ * Exit: escalate to enyo support.
242
+ * @param reason - Optional short internal key describing what failed, e.g.
243
+ * `firmware-too-old`. Never shown to the installer; it travels with the
244
+ * hand-off so support knows why it arrived.
245
+ */
246
+ support: (reason) => ({
247
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Support,
248
+ reason,
249
+ }),
196
250
  /**
197
251
  * Exit: pause the run (resumable) with a reason.
252
+ *
253
+ * For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
254
+ * {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
255
+ * hand-off, not a park, and `resumeStepName` does not apply to it.
256
+ *
198
257
  * @param reason - Why the run is parked.
199
258
  * @param resumeStepName - Optional step `name` to resume at.
200
259
  */
201
260
  pause: (reason, resumeStepName) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause, reason, resumeStepName }),
261
+ /**
262
+ * Exit: **enyo übernimmt** — the installer is done and enyo finishes the
263
+ * setup. A terminal exit alongside `success` and `support`: the app shows the
264
+ * takeover screen rather than returning to the cockpit.
265
+ *
266
+ * Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
267
+ * counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
268
+ * that only ends here needs no `success` branch.
269
+ */
270
+ enyoTakeover: () => ({
271
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause,
272
+ reason: enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo,
273
+ }),
202
274
  /**
203
275
  * Jump into another start variant's flow for the same vendor/model.
204
276
  * @param variant - The start variant to hand off to.
@@ -10,8 +10,8 @@
10
10
  * before publishing.
11
11
  */
12
12
  import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
13
- import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection } from '../../types/enyo-onboarding-v2.cjs';
14
- import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2PauseReason, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
13
+ import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
14
+ import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2AuthOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
15
15
  /**
16
16
  * Identity helper that type-checks a v2 guide literal at definition time
17
17
  * (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
@@ -98,6 +98,42 @@ export declare const onboardingV2Block: {
98
98
  * @param deviceSelection - Which devices to test (defaults to `Detected`).
99
99
  */
100
100
  deviceTest: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[], deviceSelection?: EnyoOnboardingV2DeviceSelection) => EnyoOnboardingV2Block;
101
+ /**
102
+ * An OCPP-connect action block: wait for the charger to dial into enyo's
103
+ * CSMS after the installer has entered the dynamic OCPP URL in it.
104
+ *
105
+ * A convenience wrapper over {@link onboardingV2Block.action} that pins the
106
+ * action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
107
+ * {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
108
+ * with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
109
+ * preceding step.
110
+ *
111
+ * Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
112
+ * and both must be wired — the validator enforces that, since a charger that
113
+ * never calls home is the common case.
114
+ *
115
+ * @param id - Stable block id, unique within the guide.
116
+ * @param label - Translated trigger button text (de/en).
117
+ * @param outcomes - The `connected` / `timeout` results; each is a routing handle.
118
+ */
119
+ ocppConnect: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
120
+ /**
121
+ * An auth block: the installer signs into the energy app's own account
122
+ * system (OAuth / vendor portal).
123
+ *
124
+ * Exactly one routing handle, and it means "the login succeeded". The server
125
+ * decides when it fires, so the installer cannot skip it; there is no failure
126
+ * branch to author — a failed attempt simply keeps them on the step. Route
127
+ * the handle with {@link onOutcomeV2}, passing `outcome.id`.
128
+ *
129
+ * @param id - Stable block id, unique within the guide.
130
+ * @param label - Translated sign-in button text (de/en).
131
+ * @param outcome - The single success handle (`{id, label}`).
132
+ * @param opts - Optional translated `help` naming the account that is needed.
133
+ */
134
+ auth: (id: string, label: EnyoOnboardingTranslatedContent[], outcome: EnyoOnboardingV2AuthOutcome, opts?: {
135
+ help?: EnyoOnboardingTranslatedContent[];
136
+ }) => EnyoOnboardingV2Block;
101
137
  /**
102
138
  * A link block: a fixed URL the installer opens or copies.
103
139
  *
@@ -143,14 +179,34 @@ export declare const onboardingV2Target: {
143
179
  step: (stepId: string) => EnyoOnboardingV2Target;
144
180
  /** Exit: onboarding succeeded (hand back to the app). */
145
181
  success: () => EnyoOnboardingV2Target;
146
- /** Exit: escalate to enyo support. */
147
- support: () => EnyoOnboardingV2Target;
182
+ /**
183
+ * Exit: escalate to enyo support.
184
+ * @param reason - Optional short internal key describing what failed, e.g.
185
+ * `firmware-too-old`. Never shown to the installer; it travels with the
186
+ * hand-off so support knows why it arrived.
187
+ */
188
+ support: (reason?: string) => EnyoOnboardingV2Target;
148
189
  /**
149
190
  * Exit: pause the run (resumable) with a reason.
191
+ *
192
+ * For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
193
+ * {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
194
+ * hand-off, not a park, and `resumeStepName` does not apply to it.
195
+ *
150
196
  * @param reason - Why the run is parked.
151
197
  * @param resumeStepName - Optional step `name` to resume at.
152
198
  */
153
199
  pause: (reason: EnyoOnboardingV2PauseReason, resumeStepName?: string) => EnyoOnboardingV2Target;
200
+ /**
201
+ * Exit: **enyo übernimmt** — the installer is done and enyo finishes the
202
+ * setup. A terminal exit alongside `success` and `support`: the app shows the
203
+ * takeover screen rather than returning to the cockpit.
204
+ *
205
+ * Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
206
+ * counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
207
+ * that only ends here needs no `success` branch.
208
+ */
209
+ enyoTakeover: () => EnyoOnboardingV2Target;
154
210
  /**
155
211
  * Jump into another start variant's flow for the same vendor/model.
156
212
  * @param variant - The start variant to hand off to.
@@ -5,8 +5,14 @@
5
5
  * energy app can fail fast locally before publishing.
6
6
  *
7
7
  * `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
8
- * path to success). Use {@link validateOnboardingGuideV2} for the non-throwing
9
- * result, or {@link assertValidOnboardingGuideV2} to throw on the first failure.
8
+ * path to a completing exit). Use {@link validateOnboardingGuideV2} for the
9
+ * non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
10
+ * first failure.
11
+ *
12
+ * A run completes either through a `success` target **or** through the
13
+ * "enyo übernimmt" hand-off (`pause` with reason
14
+ * {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
15
+ * legitimately ends in a hand-off is not nagged about a missing success path.
10
16
  */
11
17
  Object.defineProperty(exports, "__esModule", { value: true });
12
18
  exports.OnboardingV2ValidationError = void 0;
@@ -34,8 +40,8 @@ exports.OnboardingV2ValidationError = OnboardingV2ValidationError;
34
40
  const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
35
41
  /**
36
42
  * Structural + plausibility validation of a v2 guide graph. `errors` block
37
- * publishing; `warnings` are advisory (e.g. unreachable step, no path to
38
- * success).
43
+ * publishing; `warnings` are advisory (e.g. unreachable step, no path to a
44
+ * completing exit — `success` or the `enyo-todo` hand-off).
39
45
  *
40
46
  * @param guide - The v2 guide to validate.
41
47
  * @returns The {@link OnboardingV2ValidationResult}.
@@ -72,11 +78,12 @@ function validateOnboardingGuideV2(guide) {
72
78
  validateActionBlocks(step, at, errors, warnings);
73
79
  validateLinkBlocks(step, at, errors, warnings);
74
80
  validateInputBlocks(step, at, errors, warnings);
81
+ validateAuthBlocks(step, at, errors, warnings);
75
82
  }
76
83
  if (!guide.startStepId || !stepIds.has(guide.startStepId)) {
77
84
  errors.push('`startStepId` must reference an existing step.');
78
85
  }
79
- let hasSuccess = false;
86
+ let hasCompletingExit = false;
80
87
  for (const [i, step] of guide.steps.entries()) {
81
88
  const at = `steps[${i}] (${step.name})`;
82
89
  const required = requiredHandleKeys(step);
@@ -90,14 +97,24 @@ function validateOnboardingGuideV2(guide) {
90
97
  wired.add(key);
91
98
  const tg = t.target;
92
99
  if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success)
93
- hasSuccess = true;
100
+ hasCompletingExit = true;
94
101
  else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step && !stepIds.has(tg.stepId)) {
95
102
  errors.push(`${at}: transition targets missing step "${tg.stepId}".`);
96
103
  }
97
- else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause &&
98
- tg.resumeStepName &&
99
- !stepNames.has(tg.resumeStepName)) {
100
- errors.push(`${at}: pause resumeStepName "${tg.resumeStepName}" is unknown.`);
104
+ else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause) {
105
+ // "enyo übernimmt" is a terminal hand-off, not a park: it completes
106
+ // the run for the installer, so it satisfies the completion check
107
+ // and has nothing to resume at.
108
+ if (tg.reason === enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo) {
109
+ hasCompletingExit = true;
110
+ if (tg.resumeStepName) {
111
+ warnings.push(`${at}: pause reason "${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" is a hand-off to enyo, ` +
112
+ `not a resumable park — resumeStepName "${tg.resumeStepName}" is ignored.`);
113
+ }
114
+ }
115
+ else if (tg.resumeStepName && !stepNames.has(tg.resumeStepName)) {
116
+ errors.push(`${at}: pause resumeStepName "${tg.resumeStepName}" is unknown.`);
117
+ }
101
118
  }
102
119
  else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.StartVariant &&
103
120
  tg.variant === guide.startVariant) {
@@ -117,8 +134,11 @@ function validateOnboardingGuideV2(guide) {
117
134
  warnings.push(`step "${step.name}" is unreachable from the start.`);
118
135
  }
119
136
  }
120
- if (!hasSuccess)
121
- warnings.push('No branch reaches a `success` exit.');
137
+ if (!hasCompletingExit) {
138
+ warnings.push('No branch reaches a completing exit — wire a `success` target, or a `pause` with reason ' +
139
+ `"${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" when enyo takes the setup over.`);
140
+ }
141
+ validateNetworkScanFlag(guide, warnings);
122
142
  return { ok: errors.length === 0, errors, warnings };
123
143
  }
124
144
  /**
@@ -155,6 +175,9 @@ function validateActionBlocks(step, at, errors, warnings) {
155
175
  if (block.deviceSelection) {
156
176
  warnings.push(`${at}: block "${block.id}" sets deviceSelection but is not a device-test action; it is ignored.`);
157
177
  }
178
+ if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect) {
179
+ validateOcppConnectOutcomes(block, at, errors);
180
+ }
158
181
  continue;
159
182
  }
160
183
  const values = new Set();
@@ -284,6 +307,110 @@ function validateInputBlocks(step, at, errors, warnings) {
284
307
  }
285
308
  }
286
309
  }
310
+ /** Every {@link EnyoOnboardingV2OcppConnectOutcome} value. */
311
+ const OCPP_CONNECT_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2OcppConnectOutcome));
312
+ /**
313
+ * Validates the outcomes of an {@link EnyoOnboardingV2ActionKind.OcppConnect}
314
+ * block.
315
+ *
316
+ * The block waits for the charger to dial into our CSMS; it can only ever report
317
+ * that the connection arrived or that it did not, so its outcome `value`s are
318
+ * closed over {@link EnyoOnboardingV2OcppConnectOutcome}. Both must be wired: a
319
+ * charger that never calls home — wrong URL typed, no mobile coverage in the
320
+ * garage — is the *common* case, and a guide without a `timeout` branch strands
321
+ * the installer on a spinner.
322
+ *
323
+ * @param block - The ocpp-connect action block being checked.
324
+ * @param at - Human-readable location prefix for messages.
325
+ * @param errors - Collector for blocking problems.
326
+ */
327
+ function validateOcppConnectOutcomes(block, at, errors) {
328
+ const values = new Set();
329
+ for (const outcome of block.outcomes ?? []) {
330
+ if (!OCPP_CONNECT_OUTCOMES.has(outcome.value)) {
331
+ errors.push(`${at}: ocpp-connect block "${block.id}" has outcome value "${outcome.value}", which is not an EnyoOnboardingV2OcppConnectOutcome member.`);
332
+ }
333
+ else if (values.has(outcome.value)) {
334
+ errors.push(`${at}: ocpp-connect block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
335
+ }
336
+ values.add(outcome.value);
337
+ }
338
+ for (const required of OCPP_CONNECT_OUTCOMES) {
339
+ if (!values.has(required)) {
340
+ errors.push(`${at}: ocpp-connect block "${block.id}" has no "${required}" outcome; both results must be routed.`);
341
+ }
342
+ }
343
+ }
344
+ /**
345
+ * Validates the auth blocks of a step.
346
+ *
347
+ * An auth block has exactly one handle, and the **server** decides when it
348
+ * fires — there is no failure branch to author, because a failed login keeps the
349
+ * installer on the step to retry. So the checks are about the handle existing and
350
+ * being routable at all, and about the step not pretending the login is optional:
351
+ * a second decision block next to it would offer a way past a gate the client is
352
+ * not allowed to skip.
353
+ *
354
+ * @param step - The step whose blocks are checked.
355
+ * @param at - Human-readable location prefix for messages.
356
+ * @param errors - Collector for blocking problems.
357
+ * @param warnings - Collector for advisory problems.
358
+ */
359
+ function validateAuthBlocks(step, at, errors, warnings) {
360
+ const authBlocks = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth);
361
+ for (const block of authBlocks) {
362
+ if (!block.label?.length)
363
+ errors.push(`${at}: auth block "${block.id}" has no label.`);
364
+ if (!block.outcome?.id) {
365
+ errors.push(`${at}: auth block "${block.id}" has no outcome id — its success handle cannot be routed.`);
366
+ }
367
+ if (!block.outcome?.label?.length) {
368
+ warnings.push(`${at}: auth block "${block.id}" outcome has no label.`);
369
+ }
370
+ }
371
+ if (authBlocks.length > 1) {
372
+ errors.push(`${at}: more than one auth block; a step can hold at most one login.`);
373
+ }
374
+ if (authBlocks.length === 1) {
375
+ const others = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Choice ||
376
+ b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action ||
377
+ b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input);
378
+ if (others.length) {
379
+ warnings.push(`${at}: auth block "${authBlocks[0].id}" shares the step with ${others.length} other decision block(s) — ` +
380
+ 'those give the installer a way past a login the server gates.');
381
+ }
382
+ }
383
+ }
384
+ /**
385
+ * Checks {@link EnyoOnboardingV2Guide.requiresNetworkScan} against what the guide
386
+ * actually does.
387
+ *
388
+ * Opting out means "don't search, start here" — right for a device that is never
389
+ * on the LAN (an OCPP wallbox), wrong if the guide then relies on scan results.
390
+ * Both mismatches are warnings, not errors: the flag describes the host's
391
+ * behaviour before the guide runs, and an author may have a reason.
392
+ *
393
+ * @param guide - The guide being validated.
394
+ * @param warnings - Collector for advisory problems.
395
+ */
396
+ function validateNetworkScanFlag(guide, warnings) {
397
+ if (guide.requiresNetworkScan !== false)
398
+ return;
399
+ const blocks = guide.steps.flatMap((s) => s.blocks ?? []);
400
+ const scansItself = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
401
+ b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.NetworkScan);
402
+ if (scansItself)
403
+ return;
404
+ const dependsOnDetected = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
405
+ b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.DeviceTest &&
406
+ (b.deviceSelection ?? enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Detected) !==
407
+ enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Current);
408
+ if (dependsOnDetected) {
409
+ warnings.push('requiresNetworkScan is false, but a device-test block selects from detected devices — ' +
410
+ 'nothing was scanned, so it has nothing to test. Use deviceSelection "current", or run a ' +
411
+ 'network-scan action inside the guide.');
412
+ }
413
+ }
287
414
  /**
288
415
  * The set of routing-handle keys a step must wire exactly once: one per
289
416
  * choice option / action outcome / input outcome, or the single `continue`
@@ -303,6 +430,9 @@ function requiredHandleKeys(step) {
303
430
  for (const o of b.outcomes)
304
431
  keys.add(`outcome:${b.id}:${o.id}`);
305
432
  }
433
+ else if (b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth && b.outcome?.id) {
434
+ keys.add(`outcome:${b.id}:${b.outcome.id}`);
435
+ }
306
436
  }
307
437
  if (keys.size === 0)
308
438
  keys.add('continue');
@@ -4,8 +4,14 @@
4
4
  * energy app can fail fast locally before publishing.
5
5
  *
6
6
  * `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
7
- * path to success). Use {@link validateOnboardingGuideV2} for the non-throwing
8
- * result, or {@link assertValidOnboardingGuideV2} to throw on the first failure.
7
+ * path to a completing exit). Use {@link validateOnboardingGuideV2} for the
8
+ * non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
9
+ * first failure.
10
+ *
11
+ * A run completes either through a `success` target **or** through the
12
+ * "enyo übernimmt" hand-off (`pause` with reason
13
+ * {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
14
+ * legitimately ends in a hand-off is not nagged about a missing success path.
9
15
  */
10
16
  import type { EnyoOnboardingV2Guide } from '../../types/enyo-onboarding-v2.cjs';
11
17
  /**
@@ -31,8 +37,8 @@ export interface OnboardingV2ValidationResult {
31
37
  }
32
38
  /**
33
39
  * Structural + plausibility validation of a v2 guide graph. `errors` block
34
- * publishing; `warnings` are advisory (e.g. unreachable step, no path to
35
- * success).
40
+ * publishing; `warnings` are advisory (e.g. unreachable step, no path to a
41
+ * completing exit — `success` or the `enyo-todo` hand-off).
36
42
  *
37
43
  * @param guide - The v2 guide to validate.
38
44
  * @returns The {@link OnboardingV2ValidationResult}.
@@ -8,8 +8,8 @@
8
8
  * name-string routing, v2 is a **directed graph**: an energy app defines a guide
9
9
  * as a set of named steps connected by explicit {@link EnyoOnboardingV2Transition}s.
10
10
  * The installer walks it, branching on choices and device checks, and leaves through
11
- * one of three exits (success / paused / support) — or jumps into another start
12
- * variant's flow.
11
+ * one of its exits (success / enyo takeover / paused / support) — or jumps into
12
+ * another start variant's flow.
13
13
  *
14
14
  * Every author-facing string is an {@link EnyoOnboardingTranslatedContent} array
15
15
  * (de/en), reusing the v1 translation primitive so the two models stay consistent.
@@ -22,7 +22,7 @@
22
22
  * before publishing.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2InputValueType = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
25
+ exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2InputValueType = exports.EnyoOnboardingV2OcppConnectOutcome = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
26
26
  // ---------------------------------------------------------------------------
27
27
  // Enumerable string enums
28
28
  // ---------------------------------------------------------------------------
@@ -40,10 +40,27 @@ var EnyoOnboardingV2StartVariant;
40
40
  /** Manual setup is required by the user (e.g. enter OCPP URL). */
41
41
  EnyoOnboardingV2StartVariant["ManualSetup"] = "manual-setup";
42
42
  })(EnyoOnboardingV2StartVariant || (exports.EnyoOnboardingV2StartVariant = EnyoOnboardingV2StartVariant = {}));
43
- /** Why an onboarding run was parked; drives how it's picked back up. */
43
+ /**
44
+ * Why an onboarding run left the flow through a {@link EnyoOnboardingV2TargetType.Pause}
45
+ * target. The reason is not a footnote — it decides what the installer sees next
46
+ * and how (or whether) the run is picked back up.
47
+ */
44
48
  var EnyoOnboardingV2PauseReason;
45
49
  (function (EnyoOnboardingV2PauseReason) {
46
- /** enyo needs to solve something before the installer can continue. */
50
+ /**
51
+ * **enyo übernimmt** — a full hand-off, not a park.
52
+ *
53
+ * Despite living under `pause` on the wire (unchanged for compatibility),
54
+ * this is a **terminal exit alongside `success` and `support`**: the
55
+ * installer is done, enyo finishes the setup, and the app renders the
56
+ * takeover screen instead of returning to the cockpit. Nothing is scheduled
57
+ * for the installer to resume, so `resumeStepName` is meaningless here.
58
+ *
59
+ * A guide whose only end is this hand-off is complete and correct;
60
+ * {@link validateOnboardingGuideV2} treats it as a completing exit and does
61
+ * **not** warn about a missing `success` path. Never invent a fake success
62
+ * branch to silence a warning — there is none to silence.
63
+ */
47
64
  EnyoOnboardingV2PauseReason["EnyoTodo"] = "enyo-todo";
48
65
  /** The installer was contacted and follow-up is pending. */
49
66
  EnyoOnboardingV2PauseReason["InstallerContacted"] = "installer-contacted";
@@ -87,7 +104,32 @@ var EnyoOnboardingV2ActionKind;
87
104
  * which devices are passed along.
88
105
  */
89
106
  EnyoOnboardingV2ActionKind["DeviceTest"] = "device-test";
107
+ /**
108
+ * Wait for an OCPP charger to dial into enyo's CSMS.
109
+ *
110
+ * Searches nothing: an OCPP wallbox is never on the LAN to be found, so
111
+ * {@link NetworkScan} is not a substitute. The installer enters the dynamic
112
+ * OCPP URL (see {@link EnyoOnboardingV2DynamicKind.OcppUrl}) in the
113
+ * charger's own configuration; this block then waits for the resulting
114
+ * inbound connection and branches on whether it arrived.
115
+ *
116
+ * Outcome `value`s MUST be {@link EnyoOnboardingV2OcppConnectOutcome}
117
+ * members, and both of them must be wired — a charger that never calls home
118
+ * is the common case, not an edge case.
119
+ */
120
+ EnyoOnboardingV2ActionKind["OcppConnect"] = "ocpp-connect";
90
121
  })(EnyoOnboardingV2ActionKind || (exports.EnyoOnboardingV2ActionKind = EnyoOnboardingV2ActionKind = {}));
122
+ /**
123
+ * The possible results of an {@link EnyoOnboardingV2ActionKind.OcppConnect}
124
+ * block. Deliberately binary: either the charger reached our CSMS or it did not.
125
+ */
126
+ var EnyoOnboardingV2OcppConnectOutcome;
127
+ (function (EnyoOnboardingV2OcppConnectOutcome) {
128
+ /** The charger opened an OCPP connection to enyo's CSMS. */
129
+ EnyoOnboardingV2OcppConnectOutcome["Connected"] = "connected";
130
+ /** No connection arrived within the host's waiting window. */
131
+ EnyoOnboardingV2OcppConnectOutcome["Timeout"] = "timeout";
132
+ })(EnyoOnboardingV2OcppConnectOutcome || (exports.EnyoOnboardingV2OcppConnectOutcome = EnyoOnboardingV2OcppConnectOutcome = {}));
91
133
  /**
92
134
  * What an {@link EnyoOnboardingV2InputBlock} asks the installer for. Drives the
93
135
  * keyboard the app shows, the format check it applies and the seeded texts.
@@ -144,6 +186,7 @@ var EnyoOnboardingV2BlockType;
144
186
  EnyoOnboardingV2BlockType["Action"] = "action";
145
187
  EnyoOnboardingV2BlockType["Link"] = "link";
146
188
  EnyoOnboardingV2BlockType["Input"] = "input";
189
+ EnyoOnboardingV2BlockType["Auth"] = "auth";
147
190
  })(EnyoOnboardingV2BlockType || (exports.EnyoOnboardingV2BlockType = EnyoOnboardingV2BlockType = {}));
148
191
  // ---------------------------------------------------------------------------
149
192
  // Routing: transitions & targets
@@ -165,9 +208,13 @@ var EnyoOnboardingV2TargetType;
165
208
  EnyoOnboardingV2TargetType["Step"] = "step";
166
209
  /** Exit: onboarding succeeded (hand back to the app). */
167
210
  EnyoOnboardingV2TargetType["Success"] = "success";
168
- /** Exit: escalate to enyo support. */
211
+ /** Exit: escalate to enyo support (optionally with a `reason`). */
169
212
  EnyoOnboardingV2TargetType["Support"] = "support";
170
- /** Exit: pause the run (resumable) with a reason. */
213
+ /**
214
+ * Exit: leave the flow with an {@link EnyoOnboardingV2PauseReason}. Usually
215
+ * a resumable park — except {@link EnyoOnboardingV2PauseReason.EnyoTodo},
216
+ * which is the terminal "enyo übernimmt" hand-off.
217
+ */
171
218
  EnyoOnboardingV2TargetType["Pause"] = "pause";
172
219
  /** Jump into the flow of another start variant for the same vendor/model. */
173
220
  EnyoOnboardingV2TargetType["StartVariant"] = "start-variant";