@mmmnt/cli 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/moment.mjs CHANGED
@@ -55439,6 +55439,9 @@ var PolicyDeclaration = {
55439
55439
  $type: "PolicyDeclaration",
55440
55440
  action: "action",
55441
55441
  chainsTo: "chainsTo",
55442
+ conditionAfter: "conditionAfter",
55443
+ conditionField: "conditionField",
55444
+ conditionValue: "conditionValue",
55442
55445
  gateways: "gateways",
55443
55446
  name: "name",
55444
55447
  trigger: "trigger"
@@ -56130,6 +56133,18 @@ var MomentAstReflection = class extends AbstractAstReflection {
56130
56133
  chainsTo: {
56131
56134
  name: PolicyDeclaration.chainsTo
56132
56135
  },
56136
+ conditionAfter: {
56137
+ name: PolicyDeclaration.conditionAfter,
56138
+ optional: true
56139
+ },
56140
+ conditionField: {
56141
+ name: PolicyDeclaration.conditionField,
56142
+ optional: true
56143
+ },
56144
+ conditionValue: {
56145
+ name: PolicyDeclaration.conditionValue,
56146
+ optional: true
56147
+ },
56133
56148
  gateways: {
56134
56149
  name: PolicyDeclaration.gateways,
56135
56150
  defaultValue: [],
@@ -58059,6 +58074,66 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58059
58074
  "arguments": []
58060
58075
  }
58061
58076
  },
58077
+ {
58078
+ "$type": "Group",
58079
+ "elements": [
58080
+ {
58081
+ "$type": "Keyword",
58082
+ "value": "when"
58083
+ },
58084
+ {
58085
+ "$type": "Assignment",
58086
+ "feature": "conditionField",
58087
+ "operator": "=",
58088
+ "terminal": {
58089
+ "$type": "RuleCall",
58090
+ "rule": {
58091
+ "$ref": "#/rules@50"
58092
+ },
58093
+ "arguments": []
58094
+ }
58095
+ },
58096
+ {
58097
+ "$type": "Keyword",
58098
+ "value": "="
58099
+ },
58100
+ {
58101
+ "$type": "Assignment",
58102
+ "feature": "conditionValue",
58103
+ "operator": "=",
58104
+ "terminal": {
58105
+ "$type": "RuleCall",
58106
+ "rule": {
58107
+ "$ref": "#/rules@10"
58108
+ },
58109
+ "arguments": []
58110
+ }
58111
+ }
58112
+ ],
58113
+ "cardinality": "?"
58114
+ },
58115
+ {
58116
+ "$type": "Group",
58117
+ "elements": [
58118
+ {
58119
+ "$type": "Keyword",
58120
+ "value": "after"
58121
+ },
58122
+ {
58123
+ "$type": "Assignment",
58124
+ "feature": "conditionAfter",
58125
+ "operator": "=",
58126
+ "terminal": {
58127
+ "$type": "RuleCall",
58128
+ "rule": {
58129
+ "$ref": "#/rules@56"
58130
+ },
58131
+ "arguments": []
58132
+ }
58133
+ }
58134
+ ],
58135
+ "cardinality": "?"
58136
+ },
58062
58137
  {
58063
58138
  "$type": "Keyword",
58064
58139
  "value": "action"
@@ -59218,6 +59293,14 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59218
59293
  {
59219
59294
  "$type": "Keyword",
59220
59295
  "value": "note"
59296
+ },
59297
+ {
59298
+ "$type": "Keyword",
59299
+ "value": "outcome"
59300
+ },
59301
+ {
59302
+ "$type": "Keyword",
59303
+ "value": "after"
59221
59304
  }
59222
59305
  ]
59223
59306
  },
@@ -59324,6 +59407,10 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59324
59407
  {
59325
59408
  "$type": "Keyword",
59326
59409
  "value": "terminal"
59410
+ },
59411
+ {
59412
+ "$type": "Keyword",
59413
+ "value": "outcome"
59327
59414
  }
59328
59415
  ]
59329
59416
  },
@@ -59765,6 +59852,14 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59765
59852
  {
59766
59853
  "$type": "Keyword",
59767
59854
  "value": "note"
59855
+ },
59856
+ {
59857
+ "$type": "Keyword",
59858
+ "value": "outcome"
59859
+ },
59860
+ {
59861
+ "$type": "Keyword",
59862
+ "value": "after"
59768
59863
  }
59769
59864
  ]
59770
59865
  },
@@ -60291,7 +60386,7 @@ var MomentValidator = class {
60291
60386
  // those are alternative branch arms, not continuations.
60292
60387
  // =========================================================================
60293
60388
  checkTerminalIsLast(node, accept) {
60294
- if (node.modifier?.type !== "terminal")
60389
+ if (!endsFlow(node))
60295
60390
  return;
60296
60391
  const container = node.$container;
60297
60392
  const siblings = "nodes" in container ? container.nodes : [];
@@ -60762,6 +60857,10 @@ var MomentValidator = class {
60762
60857
  return false;
60763
60858
  }
60764
60859
  };
60860
+ function endsFlow(node) {
60861
+ const type = node.modifier?.type;
60862
+ return type === "terminal" || type === "outcome";
60863
+ }
60765
60864
 
60766
60865
  // ../core/dist/moment-module.js
60767
60866
  var MomentModule = {
@@ -61045,6 +61144,76 @@ function validateMultiEvent(ir, options = {}) {
61045
61144
  ];
61046
61145
  }
61047
61146
 
61147
+ // ../core/dist/validation/policy-condition.js
61148
+ function placedOrder(flow) {
61149
+ return flow.moments.flatMap((m) => [
61150
+ ...m.contextEntries.map((e) => e.nodeName),
61151
+ ...(m.branches ?? []).flatMap((b) => b.entries.map((e) => e.nodeName))
61152
+ ]);
61153
+ }
61154
+ function someFlowPlacesBefore(flows, cause, trigger) {
61155
+ let triggerPlaced = false;
61156
+ for (const flow of flows) {
61157
+ const order = placedOrder(flow);
61158
+ const firstCause = order.indexOf(cause);
61159
+ const lastTrigger = order.lastIndexOf(trigger);
61160
+ if (lastTrigger >= 0)
61161
+ triggerPlaced = true;
61162
+ if (firstCause >= 0 && lastTrigger > firstCause)
61163
+ return { reached: true, triggerPlaced };
61164
+ }
61165
+ return { reached: false, triggerPlaced };
61166
+ }
61167
+ function fieldsByEvent(ir) {
61168
+ const events = /* @__PURE__ */ new Map();
61169
+ for (const ctx of ir.contexts)
61170
+ for (const e of ctx.events) {
61171
+ const fields = events.get(e.name) ?? /* @__PURE__ */ new Set();
61172
+ for (const f of e.fields ?? [])
61173
+ fields.add(f.name);
61174
+ events.set(e.name, fields);
61175
+ }
61176
+ return events;
61177
+ }
61178
+ function v49(p, events) {
61179
+ const c = p.condition;
61180
+ const fields = events.get(p.trigger);
61181
+ if (c?.field === void 0 || fields === void 0 || fields.has(c.field))
61182
+ return [];
61183
+ return [
61184
+ {
61185
+ ruleId: "V49",
61186
+ message: `Policy '${p.name}' reacts when '${c.field}' of '${p.trigger}' is '${c.equals ?? ""}', but '${p.trigger}' has no field '${c.field}'.`
61187
+ }
61188
+ ];
61189
+ }
61190
+ function v50v51(p, events, flows) {
61191
+ const after = p.condition?.after;
61192
+ if (after === void 0)
61193
+ return [];
61194
+ if (!events.has(after))
61195
+ return [
61196
+ {
61197
+ ruleId: "V50",
61198
+ message: `Policy '${p.name}' reacts after '${after}', which is no declared event.`
61199
+ }
61200
+ ];
61201
+ const { reached, triggerPlaced } = someFlowPlacesBefore(flows, after, p.trigger);
61202
+ if (!triggerPlaced || reached)
61203
+ return [];
61204
+ return [
61205
+ {
61206
+ ruleId: "V51",
61207
+ severity: "warning",
61208
+ message: `Policy '${p.name}' reacts to '${p.trigger}' after '${after}', but no flow places '${after}' before '${p.trigger}'; the condition is never met.`
61209
+ }
61210
+ ];
61211
+ }
61212
+ function validatePolicyConditions(ir) {
61213
+ const events = fieldsByEvent(ir);
61214
+ return ir.contexts.flatMap((ctx) => ctx.policies.filter((p) => p.condition !== void 0).flatMap((p) => [...v49(p, events), ...v50v51(p, events, ir.flows ?? [])]));
61215
+ }
61216
+
61048
61217
  // ../core/dist/validation/palette-conformance.js
61049
61218
  function namesOf2(ir) {
61050
61219
  const contexts = ir.contexts;
@@ -61298,6 +61467,7 @@ function validatePaletteConformance(ir, options = {}) {
61298
61467
  ...v41v42Placements(ir, commands),
61299
61468
  ...v43(ir),
61300
61469
  ...validateMultiEvent(ir, options),
61470
+ ...validatePolicyConditions(ir),
61301
61471
  ...(ir.externals ?? []).flatMap((e) => gatewayFindings(`external '${e.name}'`, e.gateways ?? [], known))
61302
61472
  ];
61303
61473
  }
@@ -61607,6 +61777,12 @@ function transformPolicy(pol, annotations) {
61607
61777
  trigger: pol.trigger,
61608
61778
  action: pol.action,
61609
61779
  chainsTo: pol.chainsTo,
61780
+ ...pol.conditionField !== void 0 || pol.conditionAfter !== void 0 ? {
61781
+ condition: {
61782
+ ...pol.conditionField !== void 0 ? { field: pol.conditionField, equals: pol.conditionValue } : {},
61783
+ ...pol.conditionAfter !== void 0 ? { after: pol.conditionAfter } : {}
61784
+ }
61785
+ } : {},
61610
61786
  ...gatewaysOf(pol.gateways)
61611
61787
  };
61612
61788
  }
@@ -61778,13 +61954,18 @@ function transformNodeToEntry(node, laneContextMap, nodeKindMap) {
61778
61954
  if (node.cause !== void 0) {
61779
61955
  entry.cause = node.cause;
61780
61956
  }
61781
- const modifierType = node.modifier?.type;
61957
+ applyModifier(entry, node.modifier?.type);
61958
+ return entry;
61959
+ }
61960
+ function applyModifier(entry, modifierType) {
61782
61961
  if (modifierType === "optional") {
61783
61962
  entry.optional = true;
61784
61963
  } else if (modifierType === "terminal") {
61785
61964
  entry.terminal = true;
61965
+ } else if (modifierType === "outcome") {
61966
+ entry.terminal = true;
61967
+ entry.exit = "outcome";
61786
61968
  }
61787
- return entry;
61788
61969
  }
61789
61970
  function extractConnections(flow, laneContextMap) {
61790
61971
  const connections = [];
@@ -64829,6 +65010,7 @@ function multiEventPlacements(steps, commandOf) {
64829
65010
  }
64830
65011
 
64831
65012
  // ../derive/dist/engine/simulation-scenario-generator.js
65013
+ var BRANCH_KEY = "$branch";
64832
65014
  function resolveNodeKind(nodeName, contextId, contextMap, declaredKind) {
64833
65015
  const ctx = contextMap.get(contextId);
64834
65016
  if (!ctx)
@@ -64934,14 +65116,16 @@ function resolveScenarioLabel(flow, reachedBranches) {
64934
65116
  if (deviations.length === 0)
64935
65117
  return `Happy Path: ${flow.name}`;
64936
65118
  const bracket = deviations.map((b) => b.condition).join(", ");
64937
- const hasTerminalArm = deviations.some((b) => b.armHasTerminal);
64938
- return hasTerminalArm ? `Failure Path: ${flow.name} [${bracket}]` : `Variant: ${flow.name} [${bracket}]`;
65119
+ return endsAtFailureExit(deviations) ? `Failure Path: ${flow.name} [${bracket}]` : `Variant: ${flow.name} [${bracket}]`;
64939
65120
  }
64940
65121
  function resolveScenarioKind(reachedBranches) {
64941
65122
  const deviations = reachedBranches.filter((b) => !b.isFirstArm);
64942
65123
  if (deviations.length === 0)
64943
65124
  return "happy";
64944
- return deviations.some((b) => b.armHasTerminal) ? "failure" : "variant";
65125
+ return endsAtFailureExit(deviations) ? "failure" : "variant";
65126
+ }
65127
+ function endsAtFailureExit(deviations) {
65128
+ return deviations.some((b) => b.armHasFailureExit);
64945
65129
  }
64946
65130
  function buildContextMap(ir) {
64947
65131
  return new Map(ir.contexts.map((c) => [c.id, c]));
@@ -65021,6 +65205,10 @@ function selectBranch(moment, branchSelections, state) {
65021
65205
  const branch = (selected ? branches.find((b) => b.condition === selected) : void 0) ?? branches[0];
65022
65206
  return { branch, forced };
65023
65207
  }
65208
+ function armEndsAtFailureExit(branch) {
65209
+ const exit = branch.entries.find((e) => e.terminal);
65210
+ return exit !== void 0 && exit.exit !== "outcome";
65211
+ }
65024
65212
  function recordBranchPoint(state, moment, branch, branchIdx, forced) {
65025
65213
  state.activations.push({ momentName: moment.name, condition: branch.condition });
65026
65214
  if (!forced && !state.reachedNames.has(moment.name)) {
@@ -65029,7 +65217,7 @@ function recordBranchPoint(state, moment, branch, branchIdx, forced) {
65029
65217
  momentName: moment.name,
65030
65218
  condition: branch.condition,
65031
65219
  isFirstArm: branchIdx === 0,
65032
- armHasTerminal: branch.entries.some((e) => e.terminal)
65220
+ armHasFailureExit: armEndsAtFailureExit(branch)
65033
65221
  });
65034
65222
  }
65035
65223
  }
@@ -65091,6 +65279,7 @@ function generateEvents(nodes, flow, contextMap, sessionId, correlationId, baseT
65091
65279
  const eventIds = nodes.map((_, i) => `evt-${String(i + 1).padStart(3, "0")}`);
65092
65280
  const placements2 = placementsOf(nodes, contextMap);
65093
65281
  const commandOfEvent = new Map(placements2.flatMap((p) => p.events.map((k) => [k, p.command])));
65282
+ const conditioned = policyConditionedOccurrences(nodes, flow, contextMap);
65094
65283
  for (let i = 0; i < nodes.length; i++) {
65095
65284
  const node = nodes[i];
65096
65285
  const recordedBy = commandOfEvent.get(i);
@@ -65100,6 +65289,7 @@ function generateEvents(nodes, flow, contextMap, sessionId, correlationId, baseT
65100
65289
  const eventType = resolveEventType(node, effectiveKind);
65101
65290
  const productSource = resolveProductSource(node, effectiveKind, contextMap);
65102
65291
  const payload = buildPayload(node, effectiveKind, contextMap, flow);
65292
+ applyPolicyConditions(payload, i, node, conditioned, contextMap);
65103
65293
  const whenState = placements2.find((p) => p.command === i)?.whenState;
65104
65294
  if (whenState !== void 0)
65105
65295
  payload.whenState = whenState;
@@ -65117,6 +65307,89 @@ function generateEvents(nodes, flow, contextMap, sessionId, correlationId, baseT
65117
65307
  }
65118
65308
  return events;
65119
65309
  }
65310
+ function guardedPolicies(contextMap) {
65311
+ const byTrigger = /* @__PURE__ */ new Map();
65312
+ for (const ctx of contextMap.values())
65313
+ for (const policy of ctx.policies) {
65314
+ if (policy.condition?.field === void 0)
65315
+ continue;
65316
+ byTrigger.set(policy.trigger, [...byTrigger.get(policy.trigger) ?? [], policy]);
65317
+ }
65318
+ return byTrigger;
65319
+ }
65320
+ function policyConditionedOccurrences(nodes, flow, contextMap) {
65321
+ const guarded = guardedPolicies(contextMap);
65322
+ const named = /* @__PURE__ */ new Map();
65323
+ if (guarded.size === 0)
65324
+ return named;
65325
+ nodes.forEach((node, index) => {
65326
+ for (const conn of flow.connections) {
65327
+ if (!isCausationConnectionFor(conn, node))
65328
+ continue;
65329
+ const triggerName = conn.sourceNodeName ?? conn.eventId.replace(/^evt-/, "");
65330
+ const policies = (guarded.get(triggerName) ?? []).filter((p) => p.chainsTo === node.nodeName);
65331
+ if (policies.length === 0)
65332
+ continue;
65333
+ for (let j = index - 1; j >= 0; j--) {
65334
+ if (nodes[j].nodeName !== triggerName)
65335
+ continue;
65336
+ named.set(j, [...named.get(j) ?? [], ...policies]);
65337
+ break;
65338
+ }
65339
+ }
65340
+ });
65341
+ return named;
65342
+ }
65343
+ function typedValue(type, value) {
65344
+ if (type === "number" && value.trim() !== "" && !Number.isNaN(Number(value)))
65345
+ return Number(value);
65346
+ if (type === "boolean" && (value === "true" || value === "false"))
65347
+ return value === "true";
65348
+ return value;
65349
+ }
65350
+ function otherThan(value) {
65351
+ if (typeof value === "number")
65352
+ return value + 1;
65353
+ if (typeof value === "boolean")
65354
+ return !value;
65355
+ return `${String(value)}-other`;
65356
+ }
65357
+ function guardedField(policy, payload) {
65358
+ const field = policy.condition?.field;
65359
+ return field !== void 0 && field in payload ? field : void 0;
65360
+ }
65361
+ function declaredValue(evt, policy) {
65362
+ const field = policy.condition?.field ?? "";
65363
+ const type = evt.fields.find((f) => f.name === field)?.type ?? "";
65364
+ return typedValue(type, policy.condition?.equals ?? "");
65365
+ }
65366
+ function moveOffDeclared(payload, guarded, evt, fixed) {
65367
+ for (const policy of guarded) {
65368
+ const field = guardedField(policy, payload);
65369
+ if (field === void 0 || fixed.has(field))
65370
+ continue;
65371
+ const declared = declaredValue(evt, policy);
65372
+ if (payload[field] === declared)
65373
+ payload[field] = otherThan(declared);
65374
+ }
65375
+ }
65376
+ function applyPolicyConditions(payload, index, node, conditioned, contextMap) {
65377
+ const ctx = contextMap.get(node.contextId);
65378
+ const evt = ctx ? findEvent(ctx, node.nodeName) : void 0;
65379
+ if (!evt)
65380
+ return;
65381
+ const guarded = [...contextMap.values()].flatMap((c) => c.policies.filter((p) => p.trigger === node.nodeName && p.condition?.field !== void 0));
65382
+ const naming = conditioned.get(index) ?? [];
65383
+ const fixed = /* @__PURE__ */ new Set();
65384
+ for (const policy of guarded.filter((p) => naming.includes(p))) {
65385
+ const field = guardedField(policy, payload);
65386
+ if (field === void 0 || fixed.has(field))
65387
+ continue;
65388
+ payload[field] = declaredValue(evt, policy);
65389
+ fixed.add(field);
65390
+ }
65391
+ moveOffDeclared(payload, guarded, evt, fixed);
65392
+ }
65120
65393
  function findCausationEvents(node, allNodes, flow, eventIds, currentIndex, recordedBy) {
65121
65394
  const causation = [];
65122
65395
  for (const conn of flow.connections) {
@@ -65193,11 +65466,16 @@ function buildPayload(node, effectiveKind, contextMap, flow) {
65193
65466
  }
65194
65467
  }
65195
65468
  addContractFields(payload, node, flow);
65196
- if (node.outcome !== void 0) {
65197
- payload.outcome = node.outcome;
65198
- }
65469
+ stampBranch(payload, node);
65199
65470
  return payload;
65200
65471
  }
65472
+ function stampBranch(payload, node) {
65473
+ if (node.outcome === void 0)
65474
+ return;
65475
+ payload[BRANCH_KEY] = node.outcome;
65476
+ if (!("outcome" in payload))
65477
+ payload.outcome = node.outcome;
65478
+ }
65201
65479
  function addContractFields(payload, node, flow) {
65202
65480
  for (const conn of flow.connections) {
65203
65481
  if (conn.connectionType !== "crosses-to")
@@ -65278,23 +65556,40 @@ function findEventAfter(events, eventType, afterIndex) {
65278
65556
  function deriveNegativeScenarios(ir, flow, options) {
65279
65557
  const contextMap = buildContextMap(ir);
65280
65558
  const opts = resolveOptions(flow, options);
65281
- const walk = walkPath(flow, opts.branchSelections);
65282
- const nodes = walk.nodes;
65283
65559
  const scenarios = [];
65284
65560
  const usedIds = /* @__PURE__ */ new Set();
65285
- for (let i = 0; i < nodes.length; i++) {
65286
- const node = nodes[i];
65287
- const kind = resolveNodeKind(node.nodeName, node.contextId, contextMap, node.nodeKind);
65288
- if (kind !== "command")
65289
- continue;
65290
- const preconditions = findPreconditions(node, contextMap);
65291
- for (const pre of preconditions) {
65292
- const baseId = `${opts.scenarioId}-neg-${node.nodeName}-${pre.name}`;
65293
- const scenarioId = usedIds.has(baseId) ? `${baseId}-${i}` : baseId;
65294
- usedIds.add(scenarioId);
65295
- usedIds.add(baseId);
65296
- scenarios.push(buildNegativeScenario(ir, flow, walk, i, pre, contextMap, opts, scenarioId));
65561
+ const covered = /* @__PURE__ */ new Set();
65562
+ const deriveOnPath = (walk, onlyUncovered) => {
65563
+ const nodes = walk.nodes;
65564
+ for (let i = 0; i < nodes.length; i++) {
65565
+ const node = nodes[i];
65566
+ const kind = resolveNodeKind(node.nodeName, node.contextId, contextMap, node.nodeKind);
65567
+ if (kind !== "command")
65568
+ continue;
65569
+ for (const pre of findPreconditions(node, contextMap)) {
65570
+ const key = [node.nodeName, pre.name, node.cause ?? ""].join("\0");
65571
+ if (onlyUncovered && covered.has(key))
65572
+ continue;
65573
+ covered.add(key);
65574
+ const baseId = `${opts.scenarioId}-neg-${node.nodeName}-${pre.name}`;
65575
+ const scenarioId = usedIds.has(baseId) ? `${baseId}-${i}` : baseId;
65576
+ usedIds.add(scenarioId);
65577
+ usedIds.add(baseId);
65578
+ scenarios.push(buildNegativeScenario(ir, flow, walk, i, pre, contextMap, opts, scenarioId));
65579
+ }
65297
65580
  }
65581
+ };
65582
+ deriveOnPath(walkPath(flow, opts.branchSelections), false);
65583
+ if (options?.branchSelections !== void 0)
65584
+ return scenarios;
65585
+ const seenPaths = /* @__PURE__ */ new Set();
65586
+ for (const selections of enumerateBranchCombinations(flow)) {
65587
+ const walk = walkPath(flow, selections);
65588
+ const pathKey = walk.nodes.map((n) => n.nodeId).join("|");
65589
+ if (seenPaths.has(pathKey))
65590
+ continue;
65591
+ seenPaths.add(pathKey);
65592
+ deriveOnPath(walk, true);
65298
65593
  }
65299
65594
  return scenarios;
65300
65595
  }
@@ -65560,7 +65855,8 @@ function generatePolicyDefinitions(ir) {
65560
65855
  contextId: context.id,
65561
65856
  trigger: policy.trigger,
65562
65857
  ...policy.action ? { actionDescription: policy.action } : {},
65563
- chainsTo: normalizeChainsTo(policy.chainsTo)
65858
+ chainsTo: normalizeChainsTo(policy.chainsTo),
65859
+ ...policy.condition ? { condition: { ...policy.condition } } : {}
65564
65860
  });
65565
65861
  }
65566
65862
  }
@@ -66253,46 +66549,51 @@ function findProcess(nodeName, ctx) {
66253
66549
  return null;
66254
66550
  }
66255
66551
  function buildConnections(flow) {
66256
- return flow.connections.map((conn) => {
66257
- const eventType = conn.eventId.replace(/^evt-/, "");
66258
- const endpoints = {
66259
- eventType,
66260
- sourceNodeName: conn.sourceNodeName,
66261
- targetNodeName: conn.targetNodeName
66262
- };
66263
- if (conn.connectionType === "returns-to") {
66264
- if (conn.targetMomentId) {
66265
- return {
66266
- from: conn.sourceMomentId,
66267
- to: conn.targetMomentId,
66268
- style: "return",
66269
- targetMomentLabel: conn.targetMomentLabel,
66270
- ...endpoints
66271
- };
66272
- }
66552
+ return flow.connections.map((conn) => ({
66553
+ ...buildConnection(conn),
66554
+ // ADR-044: the IR already states which arm declared the connection.
66555
+ ...conn.branchCondition !== void 0 ? { branchCondition: conn.branchCondition } : {}
66556
+ }));
66557
+ }
66558
+ function buildConnection(conn) {
66559
+ const eventType = conn.eventId.replace(/^evt-/, "");
66560
+ const endpoints = {
66561
+ eventType,
66562
+ sourceNodeName: conn.sourceNodeName,
66563
+ targetNodeName: conn.targetNodeName
66564
+ };
66565
+ if (conn.connectionType === "returns-to") {
66566
+ if (conn.targetMomentId) {
66273
66567
  return {
66274
66568
  from: conn.sourceMomentId,
66275
- to: conn.targetContextId,
66276
- label: `unresolved returns-to target: ${conn.targetMomentLabel ?? "(unknown)"}`,
66569
+ to: conn.targetMomentId,
66277
66570
  style: "return",
66278
66571
  targetMomentLabel: conn.targetMomentLabel,
66279
66572
  ...endpoints
66280
66573
  };
66281
66574
  }
66282
- const isCrossing2 = conn.connectionType === "crosses-to";
66283
66575
  return {
66284
66576
  from: conn.sourceMomentId,
66285
- // For 'triggers' the core now resolves targetContextId to the context
66286
- // where the TARGET node is placed (not the source's own lane).
66287
66577
  to: conn.targetContextId,
66288
- label: isCrossing2 ? eventType : void 0,
66289
- style: mapConnectionStyle(conn),
66290
- crossBoundary: isCrossing2 || void 0,
66291
- relationship: isCrossing2 ? conn.schemaContract.relationshipType : void 0,
66292
- schemaContract: isCrossing2 ? mapSchemaContract(conn) : void 0,
66578
+ label: `unresolved returns-to target: ${conn.targetMomentLabel ?? "(unknown)"}`,
66579
+ style: "return",
66580
+ targetMomentLabel: conn.targetMomentLabel,
66293
66581
  ...endpoints
66294
66582
  };
66295
- });
66583
+ }
66584
+ const isCrossing2 = conn.connectionType === "crosses-to";
66585
+ return {
66586
+ from: conn.sourceMomentId,
66587
+ // For 'triggers' the core now resolves targetContextId to the context
66588
+ // where the TARGET node is placed (not the source's own lane).
66589
+ to: conn.targetContextId,
66590
+ label: isCrossing2 ? eventType : void 0,
66591
+ style: mapConnectionStyle(conn),
66592
+ crossBoundary: isCrossing2 || void 0,
66593
+ relationship: isCrossing2 ? conn.schemaContract.relationshipType : void 0,
66594
+ schemaContract: isCrossing2 ? mapSchemaContract(conn) : void 0,
66595
+ ...endpoints
66596
+ };
66296
66597
  }
66297
66598
  function mapConnectionStyle(conn) {
66298
66599
  if (conn.connectionType === "crosses-to")
@@ -66369,7 +66670,7 @@ function buildBranchPredicates(flow) {
66369
66670
  routes[branch.condition] = branch.condition;
66370
66671
  }
66371
66672
  predicates[moment.id] = {
66372
- field: "outcome",
66673
+ field: BRANCH_KEY,
66373
66674
  defaultRoute: moment.branches[0].condition,
66374
66675
  routes
66375
66676
  };
@@ -66565,12 +66866,16 @@ function renderNoteOnlyMoment(lines, moment, flow) {
66565
66866
  lines.push(...noteComments(moment, flow, " "));
66566
66867
  lines.push("");
66567
66868
  }
66869
+ function armEndsAtOutcome(branchEntries) {
66870
+ return branchEntries.find((e) => e.terminal)?.exit === "outcome";
66871
+ }
66568
66872
  function renderTerminalBranchScenario(lines, moment, condition, branchEntries, flow, ctxMap, ir) {
66569
- const tags = ["@terminal", "@failure-path"];
66873
+ const endsAtOutcome = armEndsAtOutcome(branchEntries);
66874
+ const tags = ["@terminal", endsAtOutcome ? "@alt-path" : "@failure-path"];
66570
66875
  lines.push(` ${tags.join(" ")}`);
66571
66876
  lines.push(` Scenario: ${moment.name} [${condition}]`);
66572
66877
  lines.push(...noteComments(moment, flow, " "));
66573
- const failedPrecondition = findFailedPrecondition(moment, ctxMap);
66878
+ const failedPrecondition = endsAtOutcome ? void 0 : findFailedPrecondition(moment, ctxMap);
66574
66879
  lines.push(` Given the ${moment.name.toLowerCase()} is evaluated`);
66575
66880
  if (failedPrecondition) {
66576
66881
  lines.push(` When ${failedPrecondition.name} is not satisfied`);
@@ -66585,7 +66890,8 @@ function renderTerminalBranchScenario(lines, moment, condition, branchEntries, f
66585
66890
  lines.push(` Then the flow terminates because ${failedPrecondition.description}`);
66586
66891
  } else {
66587
66892
  const terminalNode = branchEntries.find((e) => e.terminal)?.nodeName;
66588
- lines.push(` Then the flow terminates${terminalNode ? ` at ${terminalNode}` : ""}`);
66893
+ const verb = endsAtOutcome ? "ends" : "terminates";
66894
+ lines.push(` Then the flow ${verb}${terminalNode ? ` at ${terminalNode}` : ""}`);
66589
66895
  }
66590
66896
  const compensation = findCompensationForMoment(moment, ir);
66591
66897
  if (compensation) {
@@ -66714,7 +67020,7 @@ function readOrEmit(entry) {
66714
67020
  return "emits";
66715
67021
  }
66716
67022
  function renderInternalEventStep(lines, entry, evt, ctxName) {
66717
- const modifier = entry.optional ? " (optional)" : entry.terminal ? " (terminal)" : "";
67023
+ const modifier = entry.optional ? " (optional)" : entry.terminal ? entry.exit === "outcome" ? " (outcome)" : " (terminal)" : "";
66718
67024
  lines.push(` Then ${ctxName} ${readOrEmit(entry)} ${entry.nodeName}${modifier}`);
66719
67025
  if (evt && evt.fields.length > 0) {
66720
67026
  lines.push(` carrying ${evt.fields.map((f) => f.name).join(", ")}`);
@@ -67221,8 +67527,10 @@ var SpecificationDocumentGenerator = class {
67221
67527
  }
67222
67528
  renderTerminalBranch(lines, moment, branch, flow, ir) {
67223
67529
  const usedConnections = /* @__PURE__ */ new Set();
67224
- const terminalNode = branch.entries.find((e) => e.terminal)?.nodeName;
67225
- lines.push(` - \u274C **If ${branch.condition}:** flow terminates${terminalNode ? ` at **${terminalNode}**` : ""}`);
67530
+ const exit = branch.entries.find((e) => e.terminal);
67531
+ const terminalNode = exit?.nodeName;
67532
+ const [mark, verb] = exit?.exit === "outcome" ? ["\u2705", "ends"] : ["\u274C", "terminates"];
67533
+ lines.push(` - ${mark} **If ${branch.condition}:** flow ${verb}${terminalNode ? ` at **${terminalNode}**` : ""}`);
67226
67534
  for (const entry of branch.entries) {
67227
67535
  if (entry.terminal)
67228
67536
  continue;
@@ -67548,8 +67856,11 @@ var SpecificationDocumentGenerator = class {
67548
67856
  }
67549
67857
  renderBranchAltBlock(lines, moment, ctxName, flow, ir) {
67550
67858
  const nonTerminal = moment.branches.filter((b) => !b.entries.some((e) => e.terminal));
67551
- const terminal = moment.branches.filter((b) => b.entries.some((e) => e.terminal));
67552
- if (nonTerminal.length === 0 && terminal.length === 0)
67859
+ const ending = moment.branches.filter((b) => b.entries.some((e) => e.terminal));
67860
+ const isOutcome = (b) => b.entries.find((e) => e.terminal)?.exit === "outcome";
67861
+ const terminal = ending.filter((b) => !isOutcome(b));
67862
+ const outcome = ending.filter(isOutcome);
67863
+ if (nonTerminal.length === 0 && ending.length === 0)
67553
67864
  return;
67554
67865
  for (let i = 0; i < nonTerminal.length; i++) {
67555
67866
  const branch = nonTerminal[i];
@@ -67557,11 +67868,16 @@ var SpecificationDocumentGenerator = class {
67557
67868
  lines.push(` ${keyword} ${branch.condition}`);
67558
67869
  this.renderSequenceEntries(lines, branch.entries, moment, branch.condition, flow, ir);
67559
67870
  }
67560
- if (terminal.length > 0) {
67561
- const keyword = nonTerminal.length === 0 ? "alt" : "else";
67562
- const labels = terminal.map((b) => b.condition).join(" / ");
67563
- lines.push(` ${keyword} ${labels}`);
67564
- lines.push(` Note over ${ctxName}: Flow terminates`);
67871
+ let opened = nonTerminal.length > 0;
67872
+ for (const [arms, note] of [
67873
+ [terminal, "Flow terminates"],
67874
+ [outcome, "Flow ends at an outcome"]
67875
+ ]) {
67876
+ if (arms.length === 0)
67877
+ continue;
67878
+ lines.push(` ${opened ? "else" : "alt"} ${arms.map((b) => b.condition).join(" / ")}`);
67879
+ lines.push(` Note over ${ctxName}: ${note}`);
67880
+ opened = true;
67565
67881
  }
67566
67882
  lines.push(" end");
67567
67883
  }