@mmmnt/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/moment.mjs CHANGED
@@ -35652,6 +35652,173 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
35652
35652
  import { parseArgs } from "node:util";
35653
35653
  import { resolve as resolve5 } from "node:path";
35654
35654
 
35655
+ // ../core/dist/services/schema-validator.js
35656
+ var SchemaValidator = class {
35657
+ /**
35658
+ * Validates structural correctness of an IntermediateRepresentation.
35659
+ * Enforces SP-01 through SP-04 (pure IR checks, no I/O).
35660
+ * SP-05 (manifest file references) requires filesystem access —
35661
+ * use `validateManifestFiles()` separately.
35662
+ */
35663
+ validate(ir) {
35664
+ const diagnostics = [];
35665
+ this.checkSP01(ir, diagnostics);
35666
+ this.checkSP02(ir, diagnostics);
35667
+ this.checkSP03(ir, diagnostics);
35668
+ this.checkSP04(ir, diagnostics);
35669
+ return { valid: diagnostics.length === 0, diagnostics };
35670
+ }
35671
+ /**
35672
+ * Validates that all file references in the manifest exist on disk (SP-05).
35673
+ * Requires an injected `fileExists` function for filesystem access.
35674
+ */
35675
+ validateManifestFiles(files, fileExists) {
35676
+ const diagnostics = [];
35677
+ for (const file of files) {
35678
+ if (!fileExists(file.path)) {
35679
+ diagnostics.push({
35680
+ severity: "error",
35681
+ message: `SP-05: File '${file.path}' referenced in manifest does not exist.`,
35682
+ ruleId: "SP-05"
35683
+ });
35684
+ }
35685
+ }
35686
+ return { valid: diagnostics.length === 0, diagnostics };
35687
+ }
35688
+ checkSP01(ir, diagnostics) {
35689
+ const contextIds = new Set(ir.contexts.map((c) => c.id));
35690
+ for (const flow of ir.flows) {
35691
+ for (const conn of flow.connections) {
35692
+ if (conn.connectionType === "crosses-to" && !contextIds.has(conn.targetContextId)) {
35693
+ diagnostics.push({
35694
+ severity: "error",
35695
+ message: `SP-01: Connection '${conn.id}' references unknown context '${conn.targetContextId}'.`,
35696
+ ruleId: "SP-01"
35697
+ });
35698
+ }
35699
+ }
35700
+ }
35701
+ }
35702
+ /**
35703
+ * SP-02: A crossing's event must be declared on the boundary it crosses —
35704
+ * by the source context (the emitter, the convention the shipped examples
35705
+ * follow) or by the target context (consumer-side re-declaration). The
35706
+ * original target-only rule flagged every crossing in the canonical
35707
+ * examples, because events are declared once, in the emitting context.
35708
+ */
35709
+ checkSP02(ir, diagnostics) {
35710
+ const contextEventsMap = /* @__PURE__ */ new Map();
35711
+ for (const ctx of ir.contexts) {
35712
+ const eventIds = new Set(ctx.events.map((e) => e.id));
35713
+ contextEventsMap.set(ctx.id, eventIds);
35714
+ }
35715
+ for (const flow of ir.flows) {
35716
+ for (const conn of flow.connections) {
35717
+ if (conn.connectionType === "crosses-to") {
35718
+ this.checkCrossingEventDeclared(flow, conn, contextEventsMap, diagnostics);
35719
+ }
35720
+ }
35721
+ }
35722
+ }
35723
+ checkCrossingEventDeclared(flow, conn, contextEventsMap, diagnostics) {
35724
+ const targetEvents = contextEventsMap.get(conn.targetContextId);
35725
+ const sourceContextId = this.resolveSourceContextId(flow, conn);
35726
+ const sourceEvents = sourceContextId !== void 0 ? contextEventsMap.get(sourceContextId) : void 0;
35727
+ if (!targetEvents && !sourceEvents)
35728
+ return;
35729
+ if (!targetEvents?.has(conn.eventId) && !sourceEvents?.has(conn.eventId)) {
35730
+ diagnostics.push({
35731
+ severity: "error",
35732
+ message: `SP-02: Connection '${conn.id}' references event '${conn.eventId}' which is declared neither by its source context nor by target context '${conn.targetContextId}'.`,
35733
+ ruleId: "SP-02"
35734
+ });
35735
+ }
35736
+ }
35737
+ /** Context that owns the connection's declaring node, via its moment entry. */
35738
+ resolveSourceContextId(flow, conn) {
35739
+ if (conn.sourceNodeName === void 0)
35740
+ return void 0;
35741
+ const moment = flow.moments.find((m) => m.id === conn.sourceMomentId);
35742
+ if (!moment)
35743
+ return void 0;
35744
+ const entries = [
35745
+ ...moment.contextEntries,
35746
+ ...moment.branches?.flatMap((b) => b.entries) ?? []
35747
+ ];
35748
+ return entries.find((e) => e.nodeName === conn.sourceNodeName)?.contextId;
35749
+ }
35750
+ checkSP03(ir, diagnostics) {
35751
+ for (const flow of ir.flows) {
35752
+ for (const conn of flow.connections) {
35753
+ if (conn.connectionType === "crosses-to") {
35754
+ if (conn.schemaContract.fields.length === 0) {
35755
+ diagnostics.push({
35756
+ severity: "error",
35757
+ message: `SP-03: Crossing connection '${conn.id}' has an empty schema contract.`,
35758
+ ruleId: "SP-03"
35759
+ });
35760
+ }
35761
+ }
35762
+ }
35763
+ }
35764
+ }
35765
+ /**
35766
+ * SP-04: returns-to must land on a prior moment in the same flow.
35767
+ *
35768
+ * Resolution uses the connection's `targetMomentId` (with a
35769
+ * `targetMomentLabel` name-match fallback) — the moment the spec actually
35770
+ * names. The original implementation resolved via `targetContextId`, which
35771
+ * is the SOURCE node's own lane (audit M-P10) and flagged every moment that
35772
+ * merely shared that context — semantically wrong in both directions.
35773
+ */
35774
+ checkSP04(ir, diagnostics) {
35775
+ for (const flow of ir.flows) {
35776
+ const momentIndexMap = /* @__PURE__ */ new Map();
35777
+ flow.moments.forEach((m, index) => {
35778
+ momentIndexMap.set(m.id, index);
35779
+ });
35780
+ for (const conn of flow.connections) {
35781
+ if (conn.connectionType === "returns-to") {
35782
+ this.checkReturnsToIsPrior(flow, conn, momentIndexMap, diagnostics);
35783
+ }
35784
+ }
35785
+ }
35786
+ }
35787
+ checkReturnsToIsPrior(flow, conn, momentIndexMap, diagnostics) {
35788
+ const sourceIndex = momentIndexMap.get(conn.sourceMomentId);
35789
+ if (sourceIndex === void 0) {
35790
+ diagnostics.push({
35791
+ severity: "error",
35792
+ message: `SP-04: Connection '${conn.id}' references unresolvable source moment '${conn.sourceMomentId}'.`,
35793
+ ruleId: "SP-04"
35794
+ });
35795
+ return;
35796
+ }
35797
+ const targetMomentId = conn.targetMomentId ?? this.resolveMomentIdByLabel(flow, conn.targetMomentLabel);
35798
+ const targetIndex = targetMomentId !== void 0 ? momentIndexMap.get(targetMomentId) : void 0;
35799
+ if (targetIndex === void 0) {
35800
+ diagnostics.push({
35801
+ severity: "error",
35802
+ message: `SP-04: Connection '${conn.id}' returns-to target moment '${conn.targetMomentLabel ?? conn.targetMomentId ?? "unknown"}' which cannot be resolved in this flow.`,
35803
+ ruleId: "SP-04"
35804
+ });
35805
+ return;
35806
+ }
35807
+ if (targetIndex >= sourceIndex) {
35808
+ diagnostics.push({
35809
+ severity: "error",
35810
+ message: `SP-04: Connection '${conn.id}' returns-to a moment that is not prior (source moment index ${sourceIndex}, target moment index ${targetIndex}).`,
35811
+ ruleId: "SP-04"
35812
+ });
35813
+ }
35814
+ }
35815
+ resolveMomentIdByLabel(flow, label) {
35816
+ if (label === void 0)
35817
+ return void 0;
35818
+ return flow.moments.find((m) => m.name === label)?.id;
35819
+ }
35820
+ };
35821
+
35655
35822
  // ../core/dist/manifest/manifest-reader.js
35656
35823
  var import_yaml = __toESM(require_dist(), 1);
35657
35824
  import { readFileSync as readFileSync2, existsSync } from "node:fs";
@@ -57272,6 +57439,9 @@ var MomentFile = {
57272
57439
  contexts: "contexts",
57273
57440
  flows: "flows"
57274
57441
  };
57442
+ function isMomentFile(item) {
57443
+ return reflection2.isInstance(item, MomentFile.$type);
57444
+ }
57275
57445
  var Multiplicity = {
57276
57446
  $type: "Multiplicity",
57277
57447
  count: "count",
@@ -57320,9 +57490,10 @@ function isReturnsTo(item) {
57320
57490
  var SagaDeclaration = {
57321
57491
  $type: "SagaDeclaration",
57322
57492
  compensation: "compensation",
57493
+ initialState: "initialState",
57323
57494
  name: "name",
57324
- states: "states",
57325
57495
  timeout: "timeout",
57496
+ transitions: "transitions",
57326
57497
  trigger: "trigger"
57327
57498
  };
57328
57499
  function isSagaDeclaration(item) {
@@ -57333,6 +57504,11 @@ var SagaTimeout = {
57333
57504
  duration: "duration",
57334
57505
  none: "none"
57335
57506
  };
57507
+ var SagaTransition = {
57508
+ $type: "SagaTransition",
57509
+ event: "event",
57510
+ target: "target"
57511
+ };
57336
57512
  var TriggeredBy = {
57337
57513
  $type: "TriggeredBy",
57338
57514
  nodeName: "nodeName"
@@ -57774,16 +57950,19 @@ var MomentAstReflection = class extends AbstractAstReflection {
57774
57950
  compensation: {
57775
57951
  name: SagaDeclaration.compensation
57776
57952
  },
57953
+ initialState: {
57954
+ name: SagaDeclaration.initialState
57955
+ },
57777
57956
  name: {
57778
57957
  name: SagaDeclaration.name
57779
57958
  },
57780
- states: {
57781
- name: SagaDeclaration.states,
57782
- defaultValue: []
57783
- },
57784
57959
  timeout: {
57785
57960
  name: SagaDeclaration.timeout
57786
57961
  },
57962
+ transitions: {
57963
+ name: SagaDeclaration.transitions,
57964
+ defaultValue: []
57965
+ },
57787
57966
  trigger: {
57788
57967
  name: SagaDeclaration.trigger
57789
57968
  }
@@ -57802,6 +57981,18 @@ var MomentAstReflection = class extends AbstractAstReflection {
57802
57981
  },
57803
57982
  superTypes: []
57804
57983
  },
57984
+ SagaTransition: {
57985
+ name: SagaTransition.$type,
57986
+ properties: {
57987
+ event: {
57988
+ name: SagaTransition.event
57989
+ },
57990
+ target: {
57991
+ name: SagaTransition.target
57992
+ }
57993
+ },
57994
+ superTypes: []
57995
+ },
57805
57996
  TriggeredBy: {
57806
57997
  name: TriggeredBy.$type,
57807
57998
  properties: {
@@ -57899,7 +58090,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
57899
58090
  "terminal": {
57900
58091
  "$type": "RuleCall",
57901
58092
  "rule": {
57902
- "$ref": "#/rules@19"
58093
+ "$ref": "#/rules@20"
57903
58094
  },
57904
58095
  "arguments": []
57905
58096
  }
@@ -57927,7 +58118,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
57927
58118
  "terminal": {
57928
58119
  "$type": "RuleCall",
57929
58120
  "rule": {
57930
- "$ref": "#/rules@41"
58121
+ "$ref": "#/rules@42"
57931
58122
  },
57932
58123
  "arguments": []
57933
58124
  }
@@ -57939,7 +58130,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
57939
58130
  "terminal": {
57940
58131
  "$type": "RuleCall",
57941
58132
  "rule": {
57942
- "$ref": "#/rules@37"
58133
+ "$ref": "#/rules@38"
57943
58134
  },
57944
58135
  "arguments": []
57945
58136
  },
@@ -57959,7 +58150,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
57959
58150
  "terminal": {
57960
58151
  "$type": "RuleCall",
57961
58152
  "rule": {
57962
- "$ref": "#/rules@41"
58153
+ "$ref": "#/rules@42"
57963
58154
  },
57964
58155
  "arguments": []
57965
58156
  }
@@ -58023,7 +58214,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58023
58214
  {
58024
58215
  "$type": "RuleCall",
58025
58216
  "rule": {
58026
- "$ref": "#/rules@18"
58217
+ "$ref": "#/rules@19"
58027
58218
  },
58028
58219
  "arguments": []
58029
58220
  },
@@ -58057,7 +58248,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58057
58248
  "terminal": {
58058
58249
  "$type": "RuleCall",
58059
58250
  "rule": {
58060
- "$ref": "#/rules@40"
58251
+ "$ref": "#/rules@41"
58061
58252
  },
58062
58253
  "arguments": []
58063
58254
  }
@@ -58098,14 +58289,14 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58098
58289
  {
58099
58290
  "$type": "RuleCall",
58100
58291
  "rule": {
58101
- "$ref": "#/rules@40"
58292
+ "$ref": "#/rules@41"
58102
58293
  },
58103
58294
  "arguments": []
58104
58295
  },
58105
58296
  {
58106
58297
  "$type": "RuleCall",
58107
58298
  "rule": {
58108
- "$ref": "#/rules@41"
58299
+ "$ref": "#/rules@42"
58109
58300
  },
58110
58301
  "arguments": []
58111
58302
  }
@@ -58132,7 +58323,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58132
58323
  "terminal": {
58133
58324
  "$type": "RuleCall",
58134
58325
  "rule": {
58135
- "$ref": "#/rules@41"
58326
+ "$ref": "#/rules@42"
58136
58327
  },
58137
58328
  "arguments": []
58138
58329
  }
@@ -58148,7 +58339,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58148
58339
  "terminal": {
58149
58340
  "$type": "RuleCall",
58150
58341
  "rule": {
58151
- "$ref": "#/rules@33"
58342
+ "$ref": "#/rules@34"
58152
58343
  },
58153
58344
  "arguments": []
58154
58345
  }
@@ -58236,7 +58427,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58236
58427
  "terminal": {
58237
58428
  "$type": "RuleCall",
58238
58429
  "rule": {
58239
- "$ref": "#/rules@40"
58430
+ "$ref": "#/rules@41"
58240
58431
  },
58241
58432
  "arguments": []
58242
58433
  }
@@ -58309,7 +58500,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58309
58500
  "terminal": {
58310
58501
  "$type": "RuleCall",
58311
58502
  "rule": {
58312
- "$ref": "#/rules@40"
58503
+ "$ref": "#/rules@41"
58313
58504
  },
58314
58505
  "arguments": []
58315
58506
  }
@@ -58333,7 +58524,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58333
58524
  "terminal": {
58334
58525
  "$type": "RuleCall",
58335
58526
  "rule": {
58336
- "$ref": "#/rules@40"
58527
+ "$ref": "#/rules@41"
58337
58528
  },
58338
58529
  "arguments": []
58339
58530
  }
@@ -58349,7 +58540,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58349
58540
  "terminal": {
58350
58541
  "$type": "RuleCall",
58351
58542
  "rule": {
58352
- "$ref": "#/rules@35"
58543
+ "$ref": "#/rules@36"
58353
58544
  },
58354
58545
  "arguments": []
58355
58546
  }
@@ -58361,7 +58552,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58361
58552
  "terminal": {
58362
58553
  "$type": "RuleCall",
58363
58554
  "rule": {
58364
- "$ref": "#/rules@34"
58555
+ "$ref": "#/rules@35"
58365
58556
  },
58366
58557
  "arguments": []
58367
58558
  },
@@ -58390,7 +58581,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58390
58581
  "terminal": {
58391
58582
  "$type": "RuleCall",
58392
58583
  "rule": {
58393
- "$ref": "#/rules@40"
58584
+ "$ref": "#/rules@41"
58394
58585
  },
58395
58586
  "arguments": []
58396
58587
  }
@@ -58406,7 +58597,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58406
58597
  "terminal": {
58407
58598
  "$type": "RuleCall",
58408
58599
  "rule": {
58409
- "$ref": "#/rules@41"
58600
+ "$ref": "#/rules@42"
58410
58601
  },
58411
58602
  "arguments": []
58412
58603
  }
@@ -58434,7 +58625,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58434
58625
  "terminal": {
58435
58626
  "$type": "RuleCall",
58436
58627
  "rule": {
58437
- "$ref": "#/rules@40"
58628
+ "$ref": "#/rules@41"
58438
58629
  },
58439
58630
  "arguments": []
58440
58631
  }
@@ -58446,7 +58637,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58446
58637
  "terminal": {
58447
58638
  "$type": "RuleCall",
58448
58639
  "rule": {
58449
- "$ref": "#/rules@33"
58640
+ "$ref": "#/rules@34"
58450
58641
  },
58451
58642
  "arguments": []
58452
58643
  },
@@ -58475,7 +58666,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58475
58666
  "terminal": {
58476
58667
  "$type": "RuleCall",
58477
58668
  "rule": {
58478
- "$ref": "#/rules@40"
58669
+ "$ref": "#/rules@41"
58479
58670
  },
58480
58671
  "arguments": []
58481
58672
  }
@@ -58487,7 +58678,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58487
58678
  "terminal": {
58488
58679
  "$type": "RuleCall",
58489
58680
  "rule": {
58490
- "$ref": "#/rules@33"
58681
+ "$ref": "#/rules@34"
58491
58682
  },
58492
58683
  "arguments": []
58493
58684
  },
@@ -58516,7 +58707,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58516
58707
  "terminal": {
58517
58708
  "$type": "RuleCall",
58518
58709
  "rule": {
58519
- "$ref": "#/rules@40"
58710
+ "$ref": "#/rules@41"
58520
58711
  },
58521
58712
  "arguments": []
58522
58713
  }
@@ -58528,7 +58719,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58528
58719
  "terminal": {
58529
58720
  "$type": "RuleCall",
58530
58721
  "rule": {
58531
- "$ref": "#/rules@41"
58722
+ "$ref": "#/rules@42"
58532
58723
  },
58533
58724
  "arguments": []
58534
58725
  }
@@ -58544,7 +58735,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58544
58735
  "terminal": {
58545
58736
  "$type": "RuleCall",
58546
58737
  "rule": {
58547
- "$ref": "#/rules@40"
58738
+ "$ref": "#/rules@41"
58548
58739
  },
58549
58740
  "arguments": []
58550
58741
  }
@@ -58572,7 +58763,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58572
58763
  "terminal": {
58573
58764
  "$type": "RuleCall",
58574
58765
  "rule": {
58575
- "$ref": "#/rules@40"
58766
+ "$ref": "#/rules@41"
58576
58767
  },
58577
58768
  "arguments": []
58578
58769
  }
@@ -58588,7 +58779,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58588
58779
  "terminal": {
58589
58780
  "$type": "RuleCall",
58590
58781
  "rule": {
58591
- "$ref": "#/rules@40"
58782
+ "$ref": "#/rules@41"
58592
58783
  },
58593
58784
  "arguments": []
58594
58785
  }
@@ -58604,7 +58795,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58604
58795
  "terminal": {
58605
58796
  "$type": "RuleCall",
58606
58797
  "rule": {
58607
- "$ref": "#/rules@40"
58798
+ "$ref": "#/rules@41"
58608
58799
  },
58609
58800
  "arguments": []
58610
58801
  }
@@ -58620,7 +58811,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58620
58811
  "terminal": {
58621
58812
  "$type": "RuleCall",
58622
58813
  "rule": {
58623
- "$ref": "#/rules@41"
58814
+ "$ref": "#/rules@42"
58624
58815
  },
58625
58816
  "arguments": []
58626
58817
  }
@@ -58648,7 +58839,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58648
58839
  "terminal": {
58649
58840
  "$type": "RuleCall",
58650
58841
  "rule": {
58651
- "$ref": "#/rules@40"
58842
+ "$ref": "#/rules@41"
58652
58843
  },
58653
58844
  "arguments": []
58654
58845
  }
@@ -58680,7 +58871,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58680
58871
  "terminal": {
58681
58872
  "$type": "RuleCall",
58682
58873
  "rule": {
58683
- "$ref": "#/rules@41"
58874
+ "$ref": "#/rules@42"
58684
58875
  },
58685
58876
  "arguments": []
58686
58877
  }
@@ -58699,7 +58890,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58699
58890
  "terminal": {
58700
58891
  "$type": "RuleCall",
58701
58892
  "rule": {
58702
- "$ref": "#/rules@40"
58893
+ "$ref": "#/rules@41"
58703
58894
  },
58704
58895
  "arguments": []
58705
58896
  }
@@ -58726,7 +58917,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58726
58917
  "terminal": {
58727
58918
  "$type": "RuleCall",
58728
58919
  "rule": {
58729
- "$ref": "#/rules@40"
58920
+ "$ref": "#/rules@41"
58730
58921
  },
58731
58922
  "arguments": []
58732
58923
  }
@@ -58763,7 +58954,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58763
58954
  "terminal": {
58764
58955
  "$type": "RuleCall",
58765
58956
  "rule": {
58766
- "$ref": "#/rules@40"
58957
+ "$ref": "#/rules@41"
58767
58958
  },
58768
58959
  "arguments": []
58769
58960
  }
@@ -58779,7 +58970,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58779
58970
  "terminal": {
58780
58971
  "$type": "RuleCall",
58781
58972
  "rule": {
58782
- "$ref": "#/rules@40"
58973
+ "$ref": "#/rules@41"
58783
58974
  },
58784
58975
  "arguments": []
58785
58976
  }
@@ -58790,36 +58981,27 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58790
58981
  },
58791
58982
  {
58792
58983
  "$type": "Assignment",
58793
- "feature": "states",
58794
- "operator": "+=",
58984
+ "feature": "initialState",
58985
+ "operator": "=",
58795
58986
  "terminal": {
58796
58987
  "$type": "RuleCall",
58797
58988
  "rule": {
58798
- "$ref": "#/rules@40"
58989
+ "$ref": "#/rules@41"
58799
58990
  },
58800
58991
  "arguments": []
58801
58992
  }
58802
58993
  },
58803
58994
  {
58804
- "$type": "Group",
58805
- "elements": [
58806
- {
58807
- "$type": "Keyword",
58808
- "value": "->"
58995
+ "$type": "Assignment",
58996
+ "feature": "transitions",
58997
+ "operator": "+=",
58998
+ "terminal": {
58999
+ "$type": "RuleCall",
59000
+ "rule": {
59001
+ "$ref": "#/rules@17"
58809
59002
  },
58810
- {
58811
- "$type": "Assignment",
58812
- "feature": "states",
58813
- "operator": "+=",
58814
- "terminal": {
58815
- "$type": "RuleCall",
58816
- "rule": {
58817
- "$ref": "#/rules@40"
58818
- },
58819
- "arguments": []
58820
- }
58821
- }
58822
- ],
59003
+ "arguments": []
59004
+ },
58823
59005
  "cardinality": "*"
58824
59006
  },
58825
59007
  {
@@ -58833,7 +59015,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58833
59015
  "terminal": {
58834
59016
  "$type": "RuleCall",
58835
59017
  "rule": {
58836
- "$ref": "#/rules@41"
59018
+ "$ref": "#/rules@42"
58837
59019
  },
58838
59020
  "arguments": []
58839
59021
  }
@@ -58849,10 +59031,60 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58849
59031
  "terminal": {
58850
59032
  "$type": "RuleCall",
58851
59033
  "rule": {
58852
- "$ref": "#/rules@17"
59034
+ "$ref": "#/rules@18"
59035
+ },
59036
+ "arguments": []
59037
+ }
59038
+ }
59039
+ ]
59040
+ },
59041
+ "entry": false,
59042
+ "fragment": false,
59043
+ "parameters": []
59044
+ },
59045
+ {
59046
+ "$type": "ParserRule",
59047
+ "name": "SagaTransition",
59048
+ "definition": {
59049
+ "$type": "Group",
59050
+ "elements": [
59051
+ {
59052
+ "$type": "Keyword",
59053
+ "value": "->"
59054
+ },
59055
+ {
59056
+ "$type": "Assignment",
59057
+ "feature": "target",
59058
+ "operator": "=",
59059
+ "terminal": {
59060
+ "$type": "RuleCall",
59061
+ "rule": {
59062
+ "$ref": "#/rules@41"
58853
59063
  },
58854
59064
  "arguments": []
58855
59065
  }
59066
+ },
59067
+ {
59068
+ "$type": "Group",
59069
+ "elements": [
59070
+ {
59071
+ "$type": "Keyword",
59072
+ "value": "on"
59073
+ },
59074
+ {
59075
+ "$type": "Assignment",
59076
+ "feature": "event",
59077
+ "operator": "=",
59078
+ "terminal": {
59079
+ "$type": "RuleCall",
59080
+ "rule": {
59081
+ "$ref": "#/rules@41"
59082
+ },
59083
+ "arguments": []
59084
+ }
59085
+ }
59086
+ ],
59087
+ "cardinality": "?"
58856
59088
  }
58857
59089
  ]
58858
59090
  },
@@ -58873,7 +59105,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58873
59105
  "terminal": {
58874
59106
  "$type": "RuleCall",
58875
59107
  "rule": {
58876
- "$ref": "#/rules@40"
59108
+ "$ref": "#/rules@41"
58877
59109
  },
58878
59110
  "arguments": []
58879
59111
  }
@@ -58910,7 +59142,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58910
59142
  "terminal": {
58911
59143
  "$type": "RuleCall",
58912
59144
  "rule": {
58913
- "$ref": "#/rules@40"
59145
+ "$ref": "#/rules@41"
58914
59146
  },
58915
59147
  "arguments": []
58916
59148
  }
@@ -58926,7 +59158,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58926
59158
  "terminal": {
58927
59159
  "$type": "RuleCall",
58928
59160
  "rule": {
58929
- "$ref": "#/rules@40"
59161
+ "$ref": "#/rules@41"
58930
59162
  },
58931
59163
  "arguments": []
58932
59164
  }
@@ -58942,7 +59174,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58942
59174
  "terminal": {
58943
59175
  "$type": "RuleCall",
58944
59176
  "rule": {
58945
- "$ref": "#/rules@39"
59177
+ "$ref": "#/rules@40"
58946
59178
  },
58947
59179
  "arguments": []
58948
59180
  }
@@ -58958,7 +59190,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58958
59190
  "terminal": {
58959
59191
  "$type": "RuleCall",
58960
59192
  "rule": {
58961
- "$ref": "#/rules@41"
59193
+ "$ref": "#/rules@42"
58962
59194
  },
58963
59195
  "arguments": []
58964
59196
  }
@@ -58986,7 +59218,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
58986
59218
  "terminal": {
58987
59219
  "$type": "RuleCall",
58988
59220
  "rule": {
58989
- "$ref": "#/rules@41"
59221
+ "$ref": "#/rules@42"
58990
59222
  },
58991
59223
  "arguments": []
58992
59224
  }
@@ -59005,7 +59237,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59005
59237
  "terminal": {
59006
59238
  "$type": "RuleCall",
59007
59239
  "rule": {
59008
- "$ref": "#/rules@41"
59240
+ "$ref": "#/rules@42"
59009
59241
  },
59010
59242
  "arguments": []
59011
59243
  }
@@ -59020,7 +59252,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59020
59252
  "terminal": {
59021
59253
  "$type": "RuleCall",
59022
59254
  "rule": {
59023
- "$ref": "#/rules@20"
59255
+ "$ref": "#/rules@21"
59024
59256
  },
59025
59257
  "arguments": []
59026
59258
  },
@@ -59033,7 +59265,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59033
59265
  "terminal": {
59034
59266
  "$type": "RuleCall",
59035
59267
  "rule": {
59036
- "$ref": "#/rules@21"
59268
+ "$ref": "#/rules@22"
59037
59269
  },
59038
59270
  "arguments": []
59039
59271
  },
@@ -59076,7 +59308,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59076
59308
  "terminal": {
59077
59309
  "$type": "RuleCall",
59078
59310
  "rule": {
59079
- "$ref": "#/rules@40"
59311
+ "$ref": "#/rules@41"
59080
59312
  },
59081
59313
  "arguments": []
59082
59314
  }
@@ -59088,7 +59320,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59088
59320
  "terminal": {
59089
59321
  "$type": "RuleCall",
59090
59322
  "rule": {
59091
- "$ref": "#/rules@41"
59323
+ "$ref": "#/rules@42"
59092
59324
  },
59093
59325
  "arguments": []
59094
59326
  }
@@ -59100,7 +59332,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59100
59332
  "terminal": {
59101
59333
  "$type": "RuleCall",
59102
59334
  "rule": {
59103
- "$ref": "#/rules@37"
59335
+ "$ref": "#/rules@38"
59104
59336
  },
59105
59337
  "arguments": []
59106
59338
  },
@@ -59129,7 +59361,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59129
59361
  "terminal": {
59130
59362
  "$type": "RuleCall",
59131
59363
  "rule": {
59132
- "$ref": "#/rules@41"
59364
+ "$ref": "#/rules@42"
59133
59365
  },
59134
59366
  "arguments": []
59135
59367
  }
@@ -59154,7 +59386,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59154
59386
  "terminal": {
59155
59387
  "$type": "RuleCall",
59156
59388
  "rule": {
59157
- "$ref": "#/rules@23"
59389
+ "$ref": "#/rules@24"
59158
59390
  },
59159
59391
  "arguments": []
59160
59392
  }
@@ -59166,7 +59398,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59166
59398
  "terminal": {
59167
59399
  "$type": "RuleCall",
59168
59400
  "rule": {
59169
- "$ref": "#/rules@22"
59401
+ "$ref": "#/rules@23"
59170
59402
  },
59171
59403
  "arguments": []
59172
59404
  }
@@ -59197,7 +59429,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59197
59429
  "terminal": {
59198
59430
  "$type": "RuleCall",
59199
59431
  "rule": {
59200
- "$ref": "#/rules@40"
59432
+ "$ref": "#/rules@41"
59201
59433
  },
59202
59434
  "arguments": []
59203
59435
  }
@@ -59216,7 +59448,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59216
59448
  "terminal": {
59217
59449
  "$type": "RuleCall",
59218
59450
  "rule": {
59219
- "$ref": "#/rules@40"
59451
+ "$ref": "#/rules@41"
59220
59452
  },
59221
59453
  "arguments": []
59222
59454
  }
@@ -59235,7 +59467,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59235
59467
  "terminal": {
59236
59468
  "$type": "RuleCall",
59237
59469
  "rule": {
59238
- "$ref": "#/rules@23"
59470
+ "$ref": "#/rules@24"
59239
59471
  },
59240
59472
  "arguments": []
59241
59473
  },
@@ -59260,7 +59492,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59260
59492
  "terminal": {
59261
59493
  "$type": "RuleCall",
59262
59494
  "rule": {
59263
- "$ref": "#/rules@40"
59495
+ "$ref": "#/rules@41"
59264
59496
  },
59265
59497
  "arguments": []
59266
59498
  }
@@ -59276,7 +59508,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59276
59508
  "terminal": {
59277
59509
  "$type": "RuleCall",
59278
59510
  "rule": {
59279
- "$ref": "#/rules@40"
59511
+ "$ref": "#/rules@41"
59280
59512
  },
59281
59513
  "arguments": []
59282
59514
  }
@@ -59288,7 +59520,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59288
59520
  "terminal": {
59289
59521
  "$type": "RuleCall",
59290
59522
  "rule": {
59291
- "$ref": "#/rules@24"
59523
+ "$ref": "#/rules@25"
59292
59524
  },
59293
59525
  "arguments": []
59294
59526
  },
@@ -59301,7 +59533,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59301
59533
  "terminal": {
59302
59534
  "$type": "RuleCall",
59303
59535
  "rule": {
59304
- "$ref": "#/rules@25"
59536
+ "$ref": "#/rules@26"
59305
59537
  },
59306
59538
  "arguments": []
59307
59539
  },
@@ -59314,7 +59546,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59314
59546
  "terminal": {
59315
59547
  "$type": "RuleCall",
59316
59548
  "rule": {
59317
- "$ref": "#/rules@28"
59549
+ "$ref": "#/rules@29"
59318
59550
  },
59319
59551
  "arguments": []
59320
59552
  },
@@ -59327,7 +59559,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59327
59559
  "terminal": {
59328
59560
  "$type": "RuleCall",
59329
59561
  "rule": {
59330
- "$ref": "#/rules@27"
59562
+ "$ref": "#/rules@28"
59331
59563
  },
59332
59564
  "arguments": []
59333
59565
  },
@@ -59363,7 +59595,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59363
59595
  "terminal": {
59364
59596
  "$type": "RuleCall",
59365
59597
  "rule": {
59366
- "$ref": "#/rules@42"
59598
+ "$ref": "#/rules@43"
59367
59599
  },
59368
59600
  "arguments": []
59369
59601
  }
@@ -59375,7 +59607,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59375
59607
  "terminal": {
59376
59608
  "$type": "RuleCall",
59377
59609
  "rule": {
59378
- "$ref": "#/rules@40"
59610
+ "$ref": "#/rules@41"
59379
59611
  },
59380
59612
  "arguments": []
59381
59613
  }
@@ -59409,7 +59641,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59409
59641
  "terminal": {
59410
59642
  "$type": "RuleCall",
59411
59643
  "rule": {
59412
- "$ref": "#/rules@26"
59644
+ "$ref": "#/rules@27"
59413
59645
  },
59414
59646
  "arguments": []
59415
59647
  }
@@ -59454,21 +59686,21 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59454
59686
  {
59455
59687
  "$type": "RuleCall",
59456
59688
  "rule": {
59457
- "$ref": "#/rules@30"
59689
+ "$ref": "#/rules@31"
59458
59690
  },
59459
59691
  "arguments": []
59460
59692
  },
59461
59693
  {
59462
59694
  "$type": "RuleCall",
59463
59695
  "rule": {
59464
- "$ref": "#/rules@31"
59696
+ "$ref": "#/rules@32"
59465
59697
  },
59466
59698
  "arguments": []
59467
59699
  },
59468
59700
  {
59469
59701
  "$type": "RuleCall",
59470
59702
  "rule": {
59471
- "$ref": "#/rules@32"
59703
+ "$ref": "#/rules@33"
59472
59704
  },
59473
59705
  "arguments": []
59474
59706
  }
@@ -59495,7 +59727,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59495
59727
  "terminal": {
59496
59728
  "$type": "RuleCall",
59497
59729
  "rule": {
59498
- "$ref": "#/rules@40"
59730
+ "$ref": "#/rules@41"
59499
59731
  },
59500
59732
  "arguments": []
59501
59733
  }
@@ -59511,7 +59743,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59511
59743
  "terminal": {
59512
59744
  "$type": "RuleCall",
59513
59745
  "rule": {
59514
- "$ref": "#/rules@39"
59746
+ "$ref": "#/rules@40"
59515
59747
  },
59516
59748
  "arguments": []
59517
59749
  }
@@ -59527,7 +59759,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59527
59759
  "terminal": {
59528
59760
  "$type": "RuleCall",
59529
59761
  "rule": {
59530
- "$ref": "#/rules@29"
59762
+ "$ref": "#/rules@30"
59531
59763
  },
59532
59764
  "arguments": []
59533
59765
  },
@@ -59552,7 +59784,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59552
59784
  "terminal": {
59553
59785
  "$type": "RuleCall",
59554
59786
  "rule": {
59555
- "$ref": "#/rules@40"
59787
+ "$ref": "#/rules@41"
59556
59788
  },
59557
59789
  "arguments": []
59558
59790
  }
@@ -59568,7 +59800,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59568
59800
  "terminal": {
59569
59801
  "$type": "RuleCall",
59570
59802
  "rule": {
59571
- "$ref": "#/rules@35"
59803
+ "$ref": "#/rules@36"
59572
59804
  },
59573
59805
  "arguments": []
59574
59806
  }
@@ -59606,7 +59838,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59606
59838
  "terminal": {
59607
59839
  "$type": "RuleCall",
59608
59840
  "rule": {
59609
- "$ref": "#/rules@40"
59841
+ "$ref": "#/rules@41"
59610
59842
  },
59611
59843
  "arguments": []
59612
59844
  }
@@ -59634,7 +59866,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59634
59866
  "terminal": {
59635
59867
  "$type": "RuleCall",
59636
59868
  "rule": {
59637
- "$ref": "#/rules@40"
59869
+ "$ref": "#/rules@41"
59638
59870
  },
59639
59871
  "arguments": []
59640
59872
  }
@@ -59662,7 +59894,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59662
59894
  "terminal": {
59663
59895
  "$type": "RuleCall",
59664
59896
  "rule": {
59665
- "$ref": "#/rules@41"
59897
+ "$ref": "#/rules@42"
59666
59898
  },
59667
59899
  "arguments": []
59668
59900
  }
@@ -59686,7 +59918,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59686
59918
  "terminal": {
59687
59919
  "$type": "RuleCall",
59688
59920
  "rule": {
59689
- "$ref": "#/rules@40"
59921
+ "$ref": "#/rules@41"
59690
59922
  },
59691
59923
  "arguments": []
59692
59924
  }
@@ -59702,7 +59934,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59702
59934
  "terminal": {
59703
59935
  "$type": "RuleCall",
59704
59936
  "rule": {
59705
- "$ref": "#/rules@35"
59937
+ "$ref": "#/rules@36"
59706
59938
  },
59707
59939
  "arguments": []
59708
59940
  }
@@ -59714,7 +59946,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59714
59946
  "terminal": {
59715
59947
  "$type": "RuleCall",
59716
59948
  "rule": {
59717
- "$ref": "#/rules@34"
59949
+ "$ref": "#/rules@35"
59718
59950
  },
59719
59951
  "arguments": []
59720
59952
  },
@@ -59747,7 +59979,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59747
59979
  "terminal": {
59748
59980
  "$type": "RuleCall",
59749
59981
  "rule": {
59750
- "$ref": "#/rules@41"
59982
+ "$ref": "#/rules@42"
59751
59983
  },
59752
59984
  "arguments": []
59753
59985
  }
@@ -59763,7 +59995,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59763
59995
  "terminal": {
59764
59996
  "$type": "RuleCall",
59765
59997
  "rule": {
59766
- "$ref": "#/rules@41"
59998
+ "$ref": "#/rules@42"
59767
59999
  },
59768
60000
  "arguments": []
59769
60001
  }
@@ -59791,7 +60023,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59791
60023
  "terminal": {
59792
60024
  "$type": "RuleCall",
59793
60025
  "rule": {
59794
- "$ref": "#/rules@36"
60026
+ "$ref": "#/rules@37"
59795
60027
  },
59796
60028
  "arguments": []
59797
60029
  }
@@ -59850,7 +60082,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59850
60082
  {
59851
60083
  "$type": "RuleCall",
59852
60084
  "rule": {
59853
- "$ref": "#/rules@40"
60085
+ "$ref": "#/rules@41"
59854
60086
  },
59855
60087
  "arguments": []
59856
60088
  }
@@ -59877,7 +60109,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59877
60109
  "terminal": {
59878
60110
  "$type": "RuleCall",
59879
60111
  "rule": {
59880
- "$ref": "#/rules@38"
60112
+ "$ref": "#/rules@39"
59881
60113
  },
59882
60114
  "arguments": []
59883
60115
  }
@@ -59959,7 +60191,7 @@ var MomentGrammar = () => loadedMomentGrammar ?? (loadedMomentGrammar = loadGram
59959
60191
  {
59960
60192
  "$type": "RuleCall",
59961
60193
  "rule": {
59962
- "$ref": "#/rules@40"
60194
+ "$ref": "#/rules@41"
59963
60195
  },
59964
60196
  "arguments": []
59965
60197
  }
@@ -60061,25 +60293,90 @@ var MomentGeneratedModule = {
60061
60293
  parser: {}
60062
60294
  };
60063
60295
 
60296
+ // ../core/dist/parser/moment-sequence.js
60297
+ function buildMomentSequence(moment) {
60298
+ const positioned = [];
60299
+ moment.nodes.forEach((node, index) => {
60300
+ positioned.push({ item: { kind: "entry", index }, offset: node.$cstNode?.offset });
60301
+ });
60302
+ moment.whenBlocks.forEach((block2, index) => {
60303
+ positioned.push({ item: { kind: "branch", index }, offset: block2.$cstNode?.offset });
60304
+ });
60305
+ const fromCst = positioned.every((p) => p.offset !== void 0);
60306
+ if (!fromCst) {
60307
+ return { sequence: positioned.map((p) => p.item), fromCst: false };
60308
+ }
60309
+ const sequence = positioned.slice().sort((a, b) => a.offset - b.offset).map((p) => p.item);
60310
+ return { sequence, fromCst: true };
60311
+ }
60312
+
60064
60313
  // ../core/dist/validation/moment-validator.js
60314
+ function unquote(s) {
60315
+ return s.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s;
60316
+ }
60317
+ function buildCrossFileContext(file) {
60318
+ const declaredContextNames = [];
60319
+ const declaredEvents = /* @__PURE__ */ new Map();
60320
+ const declaredBuildingBlocks = /* @__PURE__ */ new Map();
60321
+ const declaredSagas = [];
60322
+ for (const ctx of file.contexts) {
60323
+ const contextName = unquote(ctx.name);
60324
+ declaredContextNames.push(contextName);
60325
+ const events = [];
60326
+ const blocks = [];
60327
+ for (const member of ctx.members) {
60328
+ if (isAggregateDeclaration(member)) {
60329
+ for (const aggMember of member.members) {
60330
+ if (isCommandDeclaration(aggMember)) {
60331
+ blocks.push({ name: aggMember.name, kind: "command" });
60332
+ } else if (isDomainEventDeclaration(aggMember)) {
60333
+ events.push(aggMember.name);
60334
+ blocks.push({ name: aggMember.name, kind: "event" });
60335
+ } else if (isValueObjectDeclaration(aggMember)) {
60336
+ blocks.push({ name: aggMember.name, kind: "value-object" });
60337
+ }
60338
+ }
60339
+ } else if (isPolicyDeclaration(member)) {
60340
+ blocks.push({ name: member.name, kind: "policy" });
60341
+ } else if (isSagaDeclaration(member)) {
60342
+ blocks.push({ name: member.name, kind: "saga" });
60343
+ declaredSagas.push(member.name);
60344
+ }
60345
+ }
60346
+ declaredEvents.set(contextName, events);
60347
+ declaredBuildingBlocks.set(contextName, blocks);
60348
+ }
60349
+ return { declaredContextNames, declaredEvents, declaredBuildingBlocks, declaredSagas };
60350
+ }
60065
60351
  function registerMomentValidationChecks(services) {
60066
60352
  const registry = services.validation.ValidationRegistry;
60067
60353
  const validator = services.validation.MomentValidator;
60068
60354
  const checks = {
60069
60355
  MomentDeclaration: validator.checkMoment,
60070
60356
  FieldDeclaration: validator.checkDeprecatedReplacement,
60357
+ InputField: validator.checkDeprecatedReplacement,
60071
60358
  AnnotationDeclaration: validator.checkAnnotationName,
60359
+ ContextDeclaration: validator.checkTrailingAnnotation,
60360
+ AggregateDeclaration: validator.checkTrailingAnnotation,
60361
+ SagaDeclaration: validator.checkSagaTransitionEvents,
60072
60362
  NodePlacement: [
60073
60363
  validator.checkNodePlacement,
60074
60364
  validator.checkOptionalWithCrossing,
60075
60365
  validator.checkTerminalIsLast,
60076
- validator.checkLaneIdExists
60366
+ validator.checkLaneIdExists,
60367
+ validator.checkTriggerDirection
60077
60368
  ],
60078
60369
  ContextCrossing: [
60079
60370
  validator.checkCrossingEmptyContract,
60080
- validator.checkCrossingTargetBranchLane
60371
+ validator.checkCrossingTargetBranchLane,
60372
+ validator.checkCrossingCrossFileRules
60081
60373
  ],
60082
- FlowDeclaration: [validator.checkReturnsToDepth, validator.checkBranchLaneReferenced]
60374
+ FlowDeclaration: [
60375
+ validator.checkReturnsToDepth,
60376
+ validator.checkBranchLaneReferenced,
60377
+ validator.checkV14,
60378
+ validator.checkFlowCrossFileRules
60379
+ ]
60083
60380
  };
60084
60381
  registry.register(checks, validator);
60085
60382
  }
@@ -60095,7 +60392,24 @@ function getFlowFromNode(node) {
60095
60392
  }
60096
60393
  function collectSiblingNames(field) {
60097
60394
  const container = field.$container;
60098
- return new Set(container.fields.map((f) => f.name));
60395
+ const siblings = container.fields ?? container.inputs;
60396
+ return siblings ? new Set(siblings.map((f) => f.name)) : void 0;
60397
+ }
60398
+ function laneDeclaresEvent(lane, eventName, ctx) {
60399
+ if (!lane)
60400
+ return false;
60401
+ const events = ctx.declaredEvents.get(unquote(lane.label)) ?? [];
60402
+ return events.includes(eventName);
60403
+ }
60404
+ function getMomentFileFromNode(node) {
60405
+ let current = node;
60406
+ while (current) {
60407
+ if (isMomentFile(current)) {
60408
+ return current;
60409
+ }
60410
+ current = current.$container;
60411
+ }
60412
+ return void 0;
60099
60413
  }
60100
60414
  function getMomentFromNode(node) {
60101
60415
  let current = node;
@@ -60123,6 +60437,8 @@ var MomentValidator = class {
60123
60437
  return;
60124
60438
  const replacement = field.deprecation.replacement.replace(/^"|"$/g, "");
60125
60439
  const siblings = collectSiblingNames(field);
60440
+ if (siblings === void 0)
60441
+ return;
60126
60442
  if (!siblings.has(replacement)) {
60127
60443
  accept("warning", `V12: Deprecated field '${field.name}' references replacement '${replacement}' which does not exist on this type.`, {
60128
60444
  node: field.deprecation,
@@ -60180,6 +60496,13 @@ var MomentValidator = class {
60180
60496
  }
60181
60497
  // =========================================================================
60182
60498
  // V8: [terminal] must be last node in branch/when block -> error
60499
+ //
60500
+ // M-P13 blind spot: the sibling-array check cannot see `when` blocks that
60501
+ // textually follow a terminal node in the same moment (grammar splits nodes
60502
+ // and whenBlocks into separate arrays). CST offsets recover the textual
60503
+ // order; a main-list terminal followed by a when block gets a warning.
60504
+ // A terminal INSIDE a when block followed by later when blocks is legal —
60505
+ // those are alternative branch arms, not continuations.
60183
60506
  // =========================================================================
60184
60507
  checkTerminalIsLast(node, accept) {
60185
60508
  if (node.modifier?.type !== "terminal")
@@ -60193,6 +60516,17 @@ var MomentValidator = class {
60193
60516
  property: "modifier"
60194
60517
  });
60195
60518
  }
60519
+ if (isMomentDeclaration(container)) {
60520
+ const { sequence, fromCst } = buildMomentSequence(container);
60521
+ if (!fromCst)
60522
+ return;
60523
+ const nodeIndex = container.nodes.indexOf(node);
60524
+ const position = sequence.findIndex((s) => s.kind === "entry" && s.index === nodeIndex);
60525
+ const followedByWhenBlock = position >= 0 && sequence.slice(position + 1).some((s) => s.kind === "branch");
60526
+ if (followedByWhenBlock) {
60527
+ accept("warning", "V8: Terminal node is textually followed by a when block in the same moment; the flow would continue past the terminal.", { node, property: "modifier" });
60528
+ }
60529
+ }
60196
60530
  }
60197
60531
  // =========================================================================
60198
60532
  // V13: Lane ID in node placement not matching any declared lane -> error
@@ -60230,6 +60564,110 @@ var MomentValidator = class {
60230
60564
  }
60231
60565
  }
60232
60566
  // =========================================================================
60567
+ // V19: trailing annotation classifies nothing -> warning (M-P4)
60568
+ // An annotation applies to the NEXT declaration in the member list; when
60569
+ // nothing follows it, it silently classifies nothing.
60570
+ // =========================================================================
60571
+ checkTrailingAnnotation(container, accept) {
60572
+ const members = container.members;
60573
+ for (let i = members.length - 1; i >= 0; i--) {
60574
+ const member = members[i];
60575
+ if (!isAnnotationDeclaration(member))
60576
+ break;
60577
+ accept("warning", `V19: Annotation '@${member.name}' is not followed by a declaration and has no effect.`, { node: member, property: "name" });
60578
+ }
60579
+ }
60580
+ // =========================================================================
60581
+ // V20: declared trigger direction vs flow order -> warning (M-S10)
60582
+ // `N triggers X` declares N as the cause of X, so X must not be placed
60583
+ // before N's moment; `N triggered-by X` declares X as the cause, so X must
60584
+ // not be placed after N's moment. Dangling references (X not placed in the
60585
+ // flow at all) are V1's job and are skipped here.
60586
+ // =========================================================================
60587
+ checkTriggerDirection(node, accept) {
60588
+ const flow = getFlowFromNode(node);
60589
+ const moment = getMomentFromNode(node);
60590
+ if (!flow || !moment)
60591
+ return;
60592
+ const currentIndex = flow.moments.indexOf(moment);
60593
+ if (currentIndex < 0)
60594
+ return;
60595
+ const placementIndex = this.buildPlacementIndex(flow);
60596
+ for (const conn of node.connections) {
60597
+ if (isTriggers(conn)) {
60598
+ this.warnIfPlacedBefore(conn, node, placementIndex, currentIndex, accept);
60599
+ } else if (isTriggeredBy(conn)) {
60600
+ this.warnIfPlacedAfter(conn, node, placementIndex, currentIndex, accept);
60601
+ }
60602
+ }
60603
+ }
60604
+ warnIfPlacedBefore(conn, node, placementIndex, currentIndex, accept) {
60605
+ const targetIndex = placementIndex.get(conn.nodeName);
60606
+ if (targetIndex !== void 0 && targetIndex < currentIndex) {
60607
+ accept("warning", `V20: declared trigger direction contradicts flow order: '${conn.nodeName}' occurs before '${node.nodeName}'.`, { node: conn, property: "nodeName" });
60608
+ }
60609
+ }
60610
+ warnIfPlacedAfter(conn, node, placementIndex, currentIndex, accept) {
60611
+ const sourceIndex = placementIndex.get(conn.nodeName);
60612
+ if (sourceIndex !== void 0 && sourceIndex > currentIndex) {
60613
+ accept("warning", `V20: declared trigger direction contradicts flow order: trigger '${conn.nodeName}' occurs after '${node.nodeName}'.`, { node: conn, property: "nodeName" });
60614
+ }
60615
+ }
60616
+ // =========================================================================
60617
+ // V21: saga transition event unknown -> warning (M-S6)
60618
+ // A `-> Target on Event` mapping whose event is neither declared by any
60619
+ // context in the file nor placed in any flow is probably a typo. Guarded
60620
+ // like the other cross-file checks: only meaningful when the file declares
60621
+ // contexts (sagas always live inside one); an `on` event that only appears
60622
+ // as a flow node placement is accepted, so mixed/flow-heavy specs that
60623
+ // never re-declare events in contexts do not warn.
60624
+ // =========================================================================
60625
+ checkSagaTransitionEvents(saga, accept) {
60626
+ const file = getMomentFileFromNode(saga);
60627
+ if (!file || file.contexts.length === 0)
60628
+ return;
60629
+ const knownEvents = this.collectDeclaredEventNames(file);
60630
+ const placedNodeNames = this.collectPlacedNodeNames(file);
60631
+ for (const transition of saga.transitions) {
60632
+ const event = transition.event;
60633
+ if (event === void 0)
60634
+ continue;
60635
+ if (!knownEvents.has(event) && !placedNodeNames.has(event)) {
60636
+ accept("warning", `V21: Saga transition event '${event}' is not declared by any context in this file and is not placed in any flow \u2014 possible typo.`, { node: transition, property: "event" });
60637
+ }
60638
+ }
60639
+ }
60640
+ /** Event names declared by any aggregate in any context of the file. */
60641
+ collectDeclaredEventNames(file) {
60642
+ const names = /* @__PURE__ */ new Set();
60643
+ for (const ctx of file.contexts) {
60644
+ for (const member of ctx.members) {
60645
+ if (!isAggregateDeclaration(member))
60646
+ continue;
60647
+ for (const aggMember of member.members) {
60648
+ if (isDomainEventDeclaration(aggMember))
60649
+ names.add(aggMember.name);
60650
+ }
60651
+ }
60652
+ }
60653
+ return names;
60654
+ }
60655
+ /** Node names placed anywhere in the file's flows (main lists and when blocks). */
60656
+ collectPlacedNodeNames(file) {
60657
+ const names = /* @__PURE__ */ new Set();
60658
+ for (const flow of file.flows) {
60659
+ for (const moment of flow.moments) {
60660
+ for (const node of moment.nodes)
60661
+ names.add(node.nodeName);
60662
+ for (const wb of moment.whenBlocks) {
60663
+ for (const node of wb.nodes)
60664
+ names.add(node.nodeName);
60665
+ }
60666
+ }
60667
+ }
60668
+ return names;
60669
+ }
60670
+ // =========================================================================
60233
60671
  // V17: returns-to depth capped at 1 -> warning
60234
60672
  // =========================================================================
60235
60673
  checkReturnsToDepth(flow, accept) {
@@ -60260,11 +60698,29 @@ var MomentValidator = class {
60260
60698
  }
60261
60699
  }
60262
60700
  // =========================================================================
60701
+ // Registered cross-file entry points: no-ops unless a CrossFileContext has
60702
+ // been set (single-file mode sets it only when the file declares contexts),
60703
+ // so flow-only specs are never spammed with cross-file diagnostics.
60704
+ // =========================================================================
60705
+ checkFlowCrossFileRules(flow, accept) {
60706
+ if (!this.crossFileContext)
60707
+ return;
60708
+ this.checkSP01(flow, accept, this.crossFileContext);
60709
+ }
60710
+ checkCrossingCrossFileRules(crossing, accept) {
60711
+ if (!this.crossFileContext)
60712
+ return;
60713
+ this.checkSP02(crossing, accept, this.crossFileContext);
60714
+ }
60715
+ // =========================================================================
60263
60716
  // Cross-file validators (SP-01, SP-02, V1, V9, V11, V14)
60264
60717
  // =========================================================================
60265
- // SP-01: Flow lane references must match declared context names
60718
+ // SP-01: Flow lane references must match declared context names.
60719
+ // Branch-lanes are outcome routes (e.g. "Refusals"), not contexts — skipped.
60266
60720
  checkSP01(flow, accept, ctx) {
60267
60721
  for (const lane of flow.lanes) {
60722
+ if (lane.isBranch)
60723
+ continue;
60268
60724
  if (!ctx.declaredContextNames.includes(lane.label.replace(/^"|"$/g, ""))) {
60269
60725
  accept("error", "SP-01: Lane label does not match any declared context name.", {
60270
60726
  node: lane,
@@ -60273,7 +60729,11 @@ var MomentValidator = class {
60273
60729
  }
60274
60730
  }
60275
60731
  }
60276
- // SP-02: Crossing event names must be declared events in the target context
60732
+ // SP-02: Crossing event names must be declared events on the boundary —
60733
+ // in the source context (the emitter, the convention every shipped example
60734
+ // follows) or in the target context (a consumer-side re-declaration).
60735
+ // The original target-only rule flagged every crossing in the canonical
60736
+ // examples, because events are declared once, in the emitting context.
60277
60737
  checkSP02(crossing, accept, ctx) {
60278
60738
  const flow = getFlowFromNode(crossing);
60279
60739
  if (!flow)
@@ -60281,23 +60741,27 @@ var MomentValidator = class {
60281
60741
  const targetLane = flow.lanes.find((l) => l.id === crossing.targetLaneId);
60282
60742
  if (!targetLane)
60283
60743
  return;
60284
- const contextName = targetLane.label.replace(/^"|"$/g, "");
60285
- const events = ctx.declaredEvents.get(contextName) ?? [];
60286
60744
  const parentNode = crossing.$container;
60287
- if (!events.includes(parentNode.nodeName)) {
60288
- accept("error", "SP-02: Crossing event is not declared in the target context.", {
60745
+ const sourceLane = flow.lanes.find((l) => l.id === parentNode.laneId);
60746
+ if (targetLane.isBranch || sourceLane?.isBranch)
60747
+ return;
60748
+ const declaredOnBoundary = [sourceLane, targetLane].some((lane) => laneDeclaresEvent(lane, parentNode.nodeName, ctx));
60749
+ if (!declaredOnBoundary) {
60750
+ accept("error", "SP-02: Crossing event is not declared in the source or target context.", {
60289
60751
  node: crossing,
60290
60752
  property: "targetLaneId"
60291
60753
  });
60292
60754
  }
60293
60755
  }
60294
- // V1: Node names must resolve to declared building blocks via lane context
60756
+ // V1: Node names must resolve to declared building blocks via lane context.
60757
+ // Branch-lane placements are skipped: those lanes route outcomes and do not
60758
+ // correspond to a declared context.
60295
60759
  checkV1(node, accept, ctx) {
60296
60760
  const flow = getFlowFromNode(node);
60297
60761
  if (!flow)
60298
60762
  return;
60299
60763
  const lane = flow.lanes.find((l) => l.id === node.laneId);
60300
- if (!lane)
60764
+ if (!lane || lane.isBranch)
60301
60765
  return;
60302
60766
  const contextName = lane.label.replace(/^"|"$/g, "");
60303
60767
  const blocks = ctx.declaredBuildingBlocks.get(contextName) ?? [];
@@ -60335,7 +60799,7 @@ var MomentValidator = class {
60335
60799
  if (!flow)
60336
60800
  return;
60337
60801
  const lane = flow.lanes.find((l) => l.id === node.laneId);
60338
- if (!lane)
60802
+ if (!lane || lane.isBranch)
60339
60803
  return;
60340
60804
  const contextName = lane.label.replace(/^"|"$/g, "");
60341
60805
  const blocks = ctx.declaredBuildingBlocks.get(contextName) ?? [];
@@ -60355,7 +60819,7 @@ var MomentValidator = class {
60355
60819
  if (!flow)
60356
60820
  return;
60357
60821
  const lane = flow.lanes.find((l) => l.id === node.laneId);
60358
- if (!lane)
60822
+ if (!lane || lane.isBranch)
60359
60823
  return;
60360
60824
  const contextName = lane.label.replace(/^"|"$/g, "");
60361
60825
  const blocks = ctx.declaredBuildingBlocks.get(contextName) ?? [];
@@ -60443,6 +60907,19 @@ var MomentValidator = class {
60443
60907
  }
60444
60908
  return labels;
60445
60909
  }
60910
+ /** Earliest moment index at which each node name is placed in the flow. */
60911
+ buildPlacementIndex(flow) {
60912
+ const index = /* @__PURE__ */ new Map();
60913
+ flow.moments.forEach((moment, momentIndex) => {
60914
+ const record = (n) => {
60915
+ if (!index.has(n.nodeName))
60916
+ index.set(n.nodeName, momentIndex);
60917
+ };
60918
+ moment.nodes.forEach(record);
60919
+ moment.whenBlocks.forEach((wb) => wb.nodes.forEach(record));
60920
+ });
60921
+ return index;
60922
+ }
60446
60923
  collectPriorNodeNames(flow, currentMoment) {
60447
60924
  const names = /* @__PURE__ */ new Set();
60448
60925
  for (const moment of flow.moments) {
@@ -60544,26 +61021,31 @@ var MomentModule = {
60544
61021
  };
60545
61022
 
60546
61023
  // ../core/dist/parser/ast-to-ir.js
60547
- function unquote(s) {
61024
+ function unquote2(s) {
60548
61025
  if (s.startsWith('"') && s.endsWith('"')) {
60549
61026
  return s.slice(1, -1);
60550
61027
  }
60551
61028
  return s;
60552
61029
  }
60553
- function astToIr(file) {
61030
+ function astToIr(file, metadata) {
60554
61031
  const contexts = file.contexts.map(transformContext);
60555
- const flows = file.flows.map(transformFlow);
61032
+ const nodeKindMap = buildNodeKindMap(contexts);
61033
+ const flows = file.flows.map((flow) => transformFlow(flow, nodeKindMap));
60556
61034
  const relationships = file.contexts.flatMap(extractRelationships);
60557
61035
  return {
60558
61036
  contexts,
60559
61037
  flows,
60560
61038
  glossary: [],
60561
61039
  relationships,
60562
- metadata: { name: "", version: "0.0.0" }
61040
+ metadata: {
61041
+ name: metadata?.name ?? "",
61042
+ version: metadata?.version ?? "0.0.0",
61043
+ ...metadata?.description !== void 0 ? { description: metadata.description } : {}
61044
+ }
60563
61045
  };
60564
61046
  }
60565
61047
  function transformContext(ctx) {
60566
- const name = unquote(ctx.name);
61048
+ const name = unquote2(ctx.name);
60567
61049
  const aggregates = [];
60568
61050
  const domainServices = [];
60569
61051
  const policies = [];
@@ -60588,18 +61070,18 @@ function transformContext(ctx) {
60588
61070
  allValueObjects.push(...aggDef.valueObjects);
60589
61071
  allInvariants.push(...aggDef.invariants);
60590
61072
  } else if (isDomainServiceDeclaration(member)) {
60591
- domainServices.push(transformService(member));
61073
+ domainServices.push(transformService(member, annotations));
60592
61074
  } else if (isPolicyDeclaration(member)) {
60593
- policies.push(transformPolicy(member));
61075
+ policies.push(transformPolicy(member, annotations));
60594
61076
  } else if (isSagaDeclaration(member)) {
60595
- sagas.push(transformSaga(member));
61077
+ sagas.push(transformSaga(member, annotations));
60596
61078
  }
60597
61079
  }
60598
61080
  return {
60599
61081
  id: `ctx-${name}`,
60600
61082
  name,
60601
61083
  classification: ctx.classification?.value,
60602
- description: ctx.description ? unquote(ctx.description) : void 0,
61084
+ description: ctx.description ? unquote2(ctx.description) : void 0,
60603
61085
  aggregates,
60604
61086
  domainServices,
60605
61087
  commands: allCommands,
@@ -60614,11 +61096,11 @@ function transformAnnotation(ann) {
60614
61096
  const raw = ann.value;
60615
61097
  return {
60616
61098
  name: ann.name,
60617
- value: raw.startsWith('"') ? unquote(raw) : raw
61099
+ value: raw.startsWith('"') ? unquote2(raw) : raw
60618
61100
  };
60619
61101
  }
60620
61102
  function transformAggregate(agg, annotations) {
60621
- const name = unquote(agg.name);
61103
+ const name = unquote2(agg.name);
60622
61104
  const commands = [];
60623
61105
  const events = [];
60624
61106
  const valueObjects = [];
@@ -60638,7 +61120,7 @@ function transformAggregate(agg, annotations) {
60638
61120
  } else if (isValueObjectDeclaration(member)) {
60639
61121
  valueObjects.push(transformValueObject(member, memberAnnotations));
60640
61122
  } else if (isInvariantDeclaration(member)) {
60641
- invariants.push(transformInvariant(member));
61123
+ invariants.push(transformInvariant(member, memberAnnotations));
60642
61124
  }
60643
61125
  }
60644
61126
  return {
@@ -60665,7 +61147,7 @@ function transformCommand(cmd, annotations) {
60665
61147
  function transformPrecondition(pre) {
60666
61148
  return {
60667
61149
  name: pre.name,
60668
- description: unquote(pre.description)
61150
+ description: unquote2(pre.description)
60669
61151
  };
60670
61152
  }
60671
61153
  function transformEvent(evt, annotations) {
@@ -60693,62 +61175,104 @@ function transformFieldDeclaration(field) {
60693
61175
  };
60694
61176
  if ("deprecation" in field && field.deprecation) {
60695
61177
  result.deprecated = {
60696
- reason: unquote(field.deprecation.reason),
60697
- replacement: unquote(field.deprecation.replacement)
61178
+ reason: unquote2(field.deprecation.reason),
61179
+ replacement: unquote2(field.deprecation.replacement)
60698
61180
  };
60699
61181
  }
60700
61182
  return result;
60701
61183
  }
60702
- function transformInvariant(inv) {
61184
+ function transformInvariant(inv, annotations) {
60703
61185
  return {
60704
61186
  id: inv.id,
60705
- description: unquote(inv.description),
60706
- scope: inv.scope
61187
+ description: unquote2(inv.description),
61188
+ scope: inv.scope,
61189
+ ...annotations ? { annotations } : {}
60707
61190
  };
60708
61191
  }
60709
- function transformService(svc) {
61192
+ function transformService(svc, annotations) {
60710
61193
  return {
60711
61194
  id: `svc-${svc.name}`,
60712
61195
  name: svc.name,
61196
+ ...annotations ? { annotations } : {},
60713
61197
  consumes: svc.consumes,
60714
61198
  produces: svc.produces,
60715
- description: unquote(svc.description)
61199
+ description: unquote2(svc.description)
60716
61200
  };
60717
61201
  }
60718
- function transformPolicy(pol) {
61202
+ function transformPolicy(pol, annotations) {
60719
61203
  const trigger = pol.trigger.eventName ?? "file-watcher";
60720
61204
  return {
60721
61205
  id: `pol-${pol.name}`,
60722
61206
  name: pol.name,
61207
+ ...annotations ? { annotations } : {},
60723
61208
  trigger,
60724
- action: unquote(pol.action),
61209
+ action: unquote2(pol.action),
60725
61210
  chainsTo: pol.chainsTo ?? void 0
60726
61211
  };
60727
61212
  }
60728
- function transformSaga(saga) {
61213
+ function transformSaga(saga, annotations) {
60729
61214
  const timeout = saga.timeout.duration ?? "none";
61215
+ const states = [saga.initialState, ...saga.transitions.map((t) => t.target)];
61216
+ const transitions = saga.transitions.map((t, i) => ({
61217
+ from: states[i],
61218
+ to: t.target,
61219
+ ...t.event !== void 0 ? { onEvent: t.event } : {}
61220
+ }));
60730
61221
  return {
60731
61222
  id: `saga-${saga.name}`,
60732
61223
  name: saga.name,
61224
+ ...annotations ? { annotations } : {},
60733
61225
  trigger: saga.trigger,
60734
- states: saga.states,
60735
- compensation: unquote(saga.compensation),
61226
+ states,
61227
+ transitions,
61228
+ compensation: unquote2(saga.compensation),
60736
61229
  timeout
60737
61230
  };
60738
61231
  }
60739
61232
  function buildLaneContextMap(flow) {
60740
61233
  const map2 = /* @__PURE__ */ new Map();
60741
61234
  for (const lane of flow.lanes) {
60742
- map2.set(lane.id, `ctx-${unquote(lane.label)}`);
61235
+ map2.set(lane.id, `ctx-${unquote2(lane.label)}`);
61236
+ }
61237
+ return map2;
61238
+ }
61239
+ function buildNodeKindMap(contexts) {
61240
+ const map2 = /* @__PURE__ */ new Map();
61241
+ for (const ctx of contexts) {
61242
+ for (const cmd of ctx.commands)
61243
+ map2.set(cmd.name, "command");
61244
+ for (const evt of ctx.events)
61245
+ map2.set(evt.name, "event");
61246
+ for (const policy of ctx.policies)
61247
+ map2.set(policy.name, "policy");
61248
+ for (const saga of ctx.sagas)
61249
+ map2.set(saga.name, "saga");
61250
+ }
61251
+ return map2;
61252
+ }
61253
+ function buildNodeLaneMap(flow) {
61254
+ const map2 = /* @__PURE__ */ new Map();
61255
+ const record = (node) => {
61256
+ if (!map2.has(node.nodeName)) {
61257
+ map2.set(node.nodeName, node.laneId);
61258
+ }
61259
+ };
61260
+ for (const moment of flow.moments) {
61261
+ for (const node of moment.nodes)
61262
+ record(node);
61263
+ for (const when of moment.whenBlocks) {
61264
+ for (const node of when.nodes)
61265
+ record(node);
61266
+ }
60743
61267
  }
60744
61268
  return map2;
60745
61269
  }
60746
- function transformFlow(flow) {
60747
- const name = unquote(flow.name);
61270
+ function transformFlow(flow, nodeKindMap) {
61271
+ const name = unquote2(flow.name);
60748
61272
  const laneContextMap = buildLaneContextMap(flow);
60749
61273
  const lanes = flow.lanes.map((lane) => ({
60750
61274
  id: lane.id,
60751
- label: unquote(lane.label),
61275
+ label: unquote2(lane.label),
60752
61276
  contextId: laneContextMap.get(lane.id) ?? lane.id,
60753
61277
  classification: lane.classification?.value,
60754
61278
  isBranch: lane.isBranch ?? false
@@ -60756,47 +61280,58 @@ function transformFlow(flow) {
60756
61280
  return {
60757
61281
  id: `flow-${name}`,
60758
61282
  name,
60759
- description: flow.description ? unquote(flow.description) : void 0,
61283
+ description: flow.description ? unquote2(flow.description) : void 0,
60760
61284
  lanes,
60761
- moments: flow.moments.map((moment, idx) => transformMoment(moment, idx, laneContextMap)),
61285
+ moments: flow.moments.map((moment, idx) => transformMoment(moment, idx, laneContextMap, nodeKindMap)),
60762
61286
  connections: extractConnections(flow, laneContextMap)
60763
61287
  };
60764
61288
  }
60765
- function transformMoment(moment, index, laneContextMap) {
60766
- const name = unquote(moment.label);
60767
- const contextEntries = moment.nodes.map((n) => transformNodeToEntry(n, laneContextMap));
60768
- const branches = moment.whenBlocks.length > 0 ? moment.whenBlocks.map((wb) => transformWhenBlock(wb, laneContextMap)) : void 0;
60769
- const hasTerminal = contextEntries.some((e) => e.terminal === true);
61289
+ function transformMoment(moment, index, laneContextMap, nodeKindMap) {
61290
+ const name = unquote2(moment.label);
61291
+ const contextEntries = moment.nodes.map((n) => transformNodeToEntry(n, laneContextMap, nodeKindMap));
61292
+ const branches = moment.whenBlocks.length > 0 ? moment.whenBlocks.map((wb) => transformWhenBlock(wb, laneContextMap, nodeKindMap)) : void 0;
61293
+ const hasTerminal = contextEntries.some((e) => e.terminal === true) || (branches?.some((b) => b.entries.some((e) => e.terminal === true)) ?? false);
60770
61294
  return {
60771
61295
  id: `moment-${index}-${name.replace(/\s+/g, "-")}`,
60772
61296
  name,
60773
61297
  contextEntries,
60774
61298
  branches,
60775
- terminal: hasTerminal || void 0
61299
+ terminal: hasTerminal || void 0,
61300
+ isBranch: moment.isBranch || void 0,
61301
+ // Textual order of entries/branches from CST offsets (M-P13); falls back
61302
+ // to entries-then-branches when the AST carries no CST.
61303
+ sequence: buildMomentSequence(moment).sequence
60776
61304
  };
60777
61305
  }
60778
- function transformWhenBlock(when, laneContextMap) {
61306
+ function transformWhenBlock(when, laneContextMap, nodeKindMap) {
60779
61307
  return {
60780
61308
  condition: when.condition,
60781
61309
  ...when.lane ? { lane: when.lane } : {},
60782
- entries: when.nodes.map((n) => transformNodeToEntry(n, laneContextMap))
61310
+ entries: when.nodes.map((n) => transformNodeToEntry(n, laneContextMap, nodeKindMap))
60783
61311
  };
60784
61312
  }
60785
- function transformNodeToEntry(node, laneContextMap) {
61313
+ function multiplicityOf(node) {
61314
+ if (!node.multiplicity)
61315
+ return void 0;
61316
+ return node.multiplicity.count ?? node.multiplicity.countVar;
61317
+ }
61318
+ function transformNodeToEntry(node, laneContextMap, nodeKindMap) {
60786
61319
  const contextId = laneContextMap?.get(node.laneId) ?? node.laneId;
60787
61320
  const entry = {
60788
61321
  contextId,
60789
61322
  nodeName: node.nodeName,
60790
- nodeKind: "event"
60791
- // Default — kind resolution requires cross-file context (MMNT-27)
61323
+ // Resolved from same-file context declarations; 'event' when the spec is
61324
+ // flow-only and the kind is genuinely unknown (MMNT-27).
61325
+ nodeKind: nodeKindMap?.get(node.nodeName) ?? "event"
60792
61326
  };
60793
- if (node.multiplicity) {
60794
- entry.multiplicity = node.multiplicity.count ?? node.multiplicity.countVar;
61327
+ const multiplicity = multiplicityOf(node);
61328
+ if (multiplicity !== void 0) {
61329
+ entry.multiplicity = multiplicity;
60795
61330
  }
60796
- if (node.modifier?.type === "optional") {
61331
+ const modifierType = node.modifier?.type;
61332
+ if (modifierType === "optional") {
60797
61333
  entry.optional = true;
60798
- }
60799
- if (node.modifier?.type === "terminal") {
61334
+ } else if (modifierType === "terminal") {
60800
61335
  entry.terminal = true;
60801
61336
  }
60802
61337
  return entry;
@@ -60805,10 +61340,21 @@ function extractConnections(flow, laneContextMap) {
60805
61340
  const connections = [];
60806
61341
  let connectionCounter = 0;
60807
61342
  const resolveCtx = (laneId) => laneContextMap?.get(laneId) ?? laneId;
61343
+ const nodeLaneMap = buildNodeLaneMap(flow);
61344
+ const resolveNodeCtx = (nodeName, fallbackLaneId) => {
61345
+ const laneId = nodeLaneMap.get(nodeName) ?? fallbackLaneId;
61346
+ return resolveCtx(laneId);
61347
+ };
61348
+ const momentLabelToId = /* @__PURE__ */ new Map();
61349
+ for (let momentIdx = 0; momentIdx < flow.moments.length; momentIdx++) {
61350
+ const label = unquote2(flow.moments[momentIdx].label);
61351
+ momentLabelToId.set(label, `moment-${momentIdx}-${label.replace(/\s+/g, "-")}`);
61352
+ }
60808
61353
  for (let momentIdx = 0; momentIdx < flow.moments.length; momentIdx++) {
60809
61354
  const moment = flow.moments[momentIdx];
60810
- const momentId = `moment-${momentIdx}-${unquote(moment.label).replace(/\s+/g, "-")}`;
60811
- const processNode = (node) => {
61355
+ const momentId = `moment-${momentIdx}-${unquote2(moment.label).replace(/\s+/g, "-")}`;
61356
+ const processNode = (node, branchCondition) => {
61357
+ const branchFields = branchCondition ? { branchCondition } : {};
60812
61358
  if (node.crossing) {
60813
61359
  const contract = transformCrossingToContract(node);
60814
61360
  connections.push({
@@ -60816,8 +61362,10 @@ function extractConnections(flow, laneContextMap) {
60816
61362
  sourceMomentId: momentId,
60817
61363
  targetContextId: resolveCtx(node.crossing.targetLaneId),
60818
61364
  eventId: `evt-${node.nodeName}`,
61365
+ sourceNodeName: node.nodeName,
60819
61366
  connectionType: "crosses-to",
60820
- schemaContract: contract
61367
+ schemaContract: contract,
61368
+ ...branchFields
60821
61369
  });
60822
61370
  }
60823
61371
  for (const conn of node.connections) {
@@ -60827,24 +61375,34 @@ function extractConnections(flow, laneContextMap) {
60827
61375
  sourceMomentId: momentId,
60828
61376
  targetContextId: resolveCtx(node.laneId),
60829
61377
  eventId: `evt-${conn.nodeName}`,
60830
- connectionType: "triggered-by"
61378
+ sourceNodeName: conn.nodeName,
61379
+ targetNodeName: node.nodeName,
61380
+ connectionType: "triggered-by",
61381
+ ...branchFields
60831
61382
  });
60832
61383
  } else if (isTriggers(conn)) {
60833
61384
  connections.push({
60834
61385
  id: `conn-${connectionCounter++}`,
60835
61386
  sourceMomentId: momentId,
60836
- targetContextId: resolveCtx(node.laneId),
61387
+ targetContextId: resolveNodeCtx(conn.nodeName, node.laneId),
60837
61388
  eventId: `evt-${conn.nodeName}`,
60838
- connectionType: "triggers"
61389
+ sourceNodeName: node.nodeName,
61390
+ targetNodeName: conn.nodeName,
61391
+ connectionType: "triggers",
61392
+ ...branchFields
60839
61393
  });
60840
61394
  } else if (isReturnsTo(conn)) {
61395
+ const targetLabel = unquote2(conn.frameLabel);
60841
61396
  connections.push({
60842
61397
  id: `conn-${connectionCounter++}`,
60843
61398
  sourceMomentId: momentId,
60844
61399
  targetContextId: resolveCtx(node.laneId),
60845
61400
  eventId: `evt-${node.nodeName}`,
61401
+ sourceNodeName: node.nodeName,
60846
61402
  connectionType: "returns-to",
60847
- targetMomentLabel: unquote(conn.frameLabel)
61403
+ targetMomentLabel: targetLabel,
61404
+ ...momentLabelToId.has(targetLabel) ? { targetMomentId: momentLabelToId.get(targetLabel) } : {},
61405
+ ...branchFields
60848
61406
  });
60849
61407
  }
60850
61408
  }
@@ -60854,7 +61412,7 @@ function extractConnections(flow, laneContextMap) {
60854
61412
  }
60855
61413
  for (const when of moment.whenBlocks) {
60856
61414
  for (const node of when.nodes) {
60857
- processNode(node);
61415
+ processNode(node, when.condition);
60858
61416
  }
60859
61417
  }
60860
61418
  }
@@ -60879,10 +61437,11 @@ function extractRelationships(ctx) {
60879
61437
  if (isContextRelationshipDeclaration(member)) {
60880
61438
  const rel = member;
60881
61439
  relationships.push({
60882
- sourceContextId: rel.source,
60883
- targetContextId: rel.target,
61440
+ // ctx- prefix matches ContextDefinition.id so consumers can join on id.
61441
+ sourceContextId: `ctx-${rel.source}`,
61442
+ targetContextId: `ctx-${rel.target}`,
60884
61443
  relationshipType: rel.type,
60885
- contract: unquote(rel.contract)
61444
+ contract: unquote2(rel.contract)
60886
61445
  });
60887
61446
  }
60888
61447
  }
@@ -60890,6 +61449,11 @@ function extractRelationships(ctx) {
60890
61449
  }
60891
61450
 
60892
61451
  // ../core/dist/parser/moment-parser.js
61452
+ function specNameFromPath(filePath) {
61453
+ const segments = filePath.split(/[/\\]/);
61454
+ const base = segments[segments.length - 1];
61455
+ return base.replace(/\.[^.]+$/, "");
61456
+ }
60893
61457
  var MomentParser = class {
60894
61458
  services;
60895
61459
  constructor() {
@@ -60900,7 +61464,20 @@ var MomentParser = class {
60900
61464
  }
60901
61465
  async parseContent(content, filePath) {
60902
61466
  const doc = this.services.shared.workspace.LangiumDocumentFactory.fromString(content, URI2.parse("memory:///parse.moment"));
60903
- await this.services.shared.workspace.DocumentBuilder.build([doc], { validation: true });
61467
+ const ast = doc.parseResult.value;
61468
+ const validator = this.services.validation.MomentValidator;
61469
+ const declaresContexts = ast.contexts.length > 0;
61470
+ const useCrossFileContext = declaresContexts && ast.flows.length > 0;
61471
+ if (useCrossFileContext) {
61472
+ validator.setCrossFileContext(buildCrossFileContext(ast));
61473
+ }
61474
+ try {
61475
+ await this.services.shared.workspace.DocumentBuilder.build([doc], { validation: true });
61476
+ } finally {
61477
+ if (useCrossFileContext) {
61478
+ validator.clearCrossFileContext();
61479
+ }
61480
+ }
60904
61481
  const file = filePath ?? "parse.moment";
60905
61482
  const diagnostics = [
60906
61483
  // Chevrotain lexer errors carry 1-based line/column when available.
@@ -60938,7 +61515,17 @@ var MomentParser = class {
60938
61515
  if (hasError) {
60939
61516
  return { success: false, diagnostics };
60940
61517
  }
60941
- const ir = astToIr(doc.parseResult.value);
61518
+ const ir = astToIr(ast, filePath ? { name: specNameFromPath(filePath) } : void 0);
61519
+ if (declaresContexts) {
61520
+ const schemaResult = new SchemaValidator().validate(ir);
61521
+ diagnostics.push(
61522
+ ...schemaResult.diagnostics.map((d) => ({
61523
+ severity: "warning",
61524
+ message: d.message,
61525
+ ruleId: d.ruleId
61526
+ }))
61527
+ );
61528
+ }
60942
61529
  return { success: true, ir, diagnostics };
60943
61530
  }
60944
61531
  };
@@ -63007,18 +63594,48 @@ import { parseArgs as parseArgs2 } from "node:util";
63007
63594
  import { createHash as createHash2 } from "node:crypto";
63008
63595
  function deriveTopology(ir, derivedAt) {
63009
63596
  const ctxMap = new Map(ir.contexts.map((c) => [c.id, c]));
63010
- const suites = ir.flows.map((flow) => deriveTestSuite(flow, ctxMap));
63597
+ const suites = ir.flows.map((flow, idx) => deriveTestSuite(flow, ctxMap, resolveSharedCaseFlow(ir.flows, ctxMap, idx)));
63011
63598
  const sourceIrHash = computeHash(ir);
63012
63599
  return {
63013
63600
  suites,
63014
63601
  metadata: { sourceIrHash, derivedAt: derivedAt ?? (/* @__PURE__ */ new Date()).toISOString() }
63015
63602
  };
63016
63603
  }
63017
- function deriveTestSuite(flow, ctxMap) {
63604
+ function resolveSharedCaseFlow(flows, ctxMap, flowIdx) {
63605
+ const sagas = /* @__PURE__ */ new Set();
63606
+ const policies = /* @__PURE__ */ new Set();
63607
+ const owningFlowIdx = (trigger) => {
63608
+ const idx = flows.findIndex((f) => flowContainsNode(f, trigger));
63609
+ return idx === -1 ? 0 : idx;
63610
+ };
63611
+ for (const [, ctx] of ctxMap) {
63612
+ for (const saga of ctx.sagas) {
63613
+ if (owningFlowIdx(saga.trigger) === flowIdx)
63614
+ sagas.add(saga.id);
63615
+ }
63616
+ for (const pol of ctx.policies) {
63617
+ if (pol.chainsTo && owningFlowIdx(pol.trigger) === flowIdx)
63618
+ policies.add(pol.id);
63619
+ }
63620
+ }
63621
+ return { sagas, policies };
63622
+ }
63623
+ function flowContainsNode(flow, nodeName) {
63624
+ for (const moment of flow.moments) {
63625
+ if (moment.contextEntries.some((e) => e.nodeName === nodeName))
63626
+ return true;
63627
+ for (const branch of moment.branches ?? []) {
63628
+ if (branch.entries.some((e) => e.nodeName === nodeName))
63629
+ return true;
63630
+ }
63631
+ }
63632
+ return false;
63633
+ }
63634
+ function deriveTestSuite(flow, ctxMap, sharedCases) {
63018
63635
  const testCases = flow.moments.flatMap((moment) => deriveTestCases(moment, flow));
63019
- const sagaCases = deriveSagaTestCases(ctxMap);
63636
+ const sagaCases = deriveSagaTestCases(ctxMap, sharedCases.sagas);
63020
63637
  testCases.push(...sagaCases);
63021
- const policyCases = derivePolicyChainTestCases(ctxMap);
63638
+ const policyCases = derivePolicyChainTestCases(ctxMap, sharedCases.policies);
63022
63639
  testCases.push(...policyCases);
63023
63640
  return {
63024
63641
  flowId: flow.id,
@@ -63027,10 +63644,12 @@ function deriveTestSuite(flow, ctxMap) {
63027
63644
  contextsCovered: collectContextIds(flow)
63028
63645
  };
63029
63646
  }
63030
- function deriveSagaTestCases(ctxMap) {
63647
+ function deriveSagaTestCases(ctxMap, assignedSagaIds) {
63031
63648
  const cases = [];
63032
63649
  for (const [, ctx] of ctxMap) {
63033
63650
  for (const saga of ctx.sagas) {
63651
+ if (!assignedSagaIds.has(saga.id))
63652
+ continue;
63034
63653
  cases.push(buildSagaInitCase(ctx, saga));
63035
63654
  cases.push(...buildSagaTransitionCases(ctx, saga));
63036
63655
  cases.push(buildSagaCompensationCase(ctx, saga));
@@ -63041,7 +63660,7 @@ function deriveSagaTestCases(ctxMap) {
63041
63660
  function buildSagaInitCase(ctx, saga) {
63042
63661
  return {
63043
63662
  momentId: `saga-${saga.name}-initiated`,
63044
- momentName: `Saga: ${saga.name} initiated`,
63663
+ momentName: `saga ${saga.name} starts on ${saga.trigger}`,
63045
63664
  assertions: [{
63046
63665
  crossingId: `saga-init-${saga.name}`,
63047
63666
  sourceContext: ctx.id,
@@ -63057,7 +63676,7 @@ function buildSagaTransitionCases(ctx, saga) {
63057
63676
  for (let i = 0; i < saga.states.length - 1; i++) {
63058
63677
  cases.push({
63059
63678
  momentId: `saga-${saga.name}-${saga.states[i]}-to-${saga.states[i + 1]}`,
63060
- momentName: `Saga: ${saga.name} ${saga.states[i]} \u2192 ${saga.states[i + 1]}`,
63679
+ momentName: `saga ${saga.name} transitions ${saga.states[i]} \u2192 ${saga.states[i + 1]}`,
63061
63680
  assertions: [{
63062
63681
  crossingId: `saga-transition-${saga.name}-${i}`,
63063
63682
  sourceContext: ctx.id,
@@ -63073,7 +63692,7 @@ function buildSagaTransitionCases(ctx, saga) {
63073
63692
  function buildSagaCompensationCase(ctx, saga) {
63074
63693
  return {
63075
63694
  momentId: `saga-${saga.name}-compensation`,
63076
- momentName: `Saga: ${saga.name} compensation`,
63695
+ momentName: `saga ${saga.name} compensates via ${saga.compensation} after ${saga.timeout}`,
63077
63696
  assertions: [{
63078
63697
  crossingId: `saga-comp-${saga.name}`,
63079
63698
  sourceContext: ctx.id,
@@ -63084,15 +63703,17 @@ function buildSagaCompensationCase(ctx, saga) {
63084
63703
  setupSteps: [{ contextName: ctx.name, aggregateName: saga.name, precondition: saga.compensation }]
63085
63704
  };
63086
63705
  }
63087
- function derivePolicyChainTestCases(ctxMap) {
63706
+ function derivePolicyChainTestCases(ctxMap, assignedPolicyIds) {
63088
63707
  const cases = [];
63089
63708
  for (const [, ctx] of ctxMap) {
63090
63709
  for (const pol of ctx.policies) {
63091
63710
  if (!pol.chainsTo)
63092
63711
  continue;
63712
+ if (!assignedPolicyIds.has(pol.id))
63713
+ continue;
63093
63714
  cases.push({
63094
63715
  momentId: `policy-${pol.name}`,
63095
- momentName: `Policy: ${pol.name}`,
63716
+ momentName: `policy ${pol.name} chains ${pol.trigger} \u2192 ${pol.chainsTo}`,
63096
63717
  assertions: [{
63097
63718
  crossingId: `policy-chain-${pol.name}`,
63098
63719
  sourceContext: ctx.id,
@@ -63112,20 +63733,21 @@ function derivePolicyChainTestCases(ctxMap) {
63112
63733
  }
63113
63734
  function deriveTestCases(moment, flow) {
63114
63735
  const crossings = flow.connections.filter((c) => c.sourceMomentId === moment.id && c.connectionType === "crosses-to");
63115
- const assertions = crossings.map((conn) => mapCrossingToAssertion(conn, moment));
63116
- const baseCase = {
63117
- momentId: moment.id,
63118
- momentName: moment.name,
63119
- assertions,
63120
- setupSteps: []
63121
- };
63122
63736
  if (moment.branches && moment.branches.length > 0) {
63123
63737
  return moment.branches.map((branch) => ({
63124
- ...baseCase,
63738
+ momentId: moment.id,
63739
+ momentName: moment.name,
63740
+ assertions: crossings.filter((c) => c.branchCondition === void 0 || c.branchCondition === branch.condition).map((conn) => mapCrossingToAssertion(conn, moment)),
63741
+ setupSteps: [],
63125
63742
  variant: branch.condition
63126
63743
  }));
63127
63744
  }
63128
- return [baseCase];
63745
+ return [{
63746
+ momentId: moment.id,
63747
+ momentName: moment.name,
63748
+ assertions: crossings.map((conn) => mapCrossingToAssertion(conn, moment)),
63749
+ setupSteps: []
63750
+ }];
63129
63751
  }
63130
63752
  function mapCrossingToAssertion(conn, moment) {
63131
63753
  const sourceContextId = resolveSourceContextId(conn, moment);
@@ -63177,13 +63799,52 @@ function computeHash(ir) {
63177
63799
  return createHash2("sha256").update(JSON.stringify(ir)).digest("hex");
63178
63800
  }
63179
63801
 
63802
+ // ../derive/dist/engine/moment-children.js
63803
+ function orderedMomentChildren(moment, fallback) {
63804
+ const entries = moment.contextEntries;
63805
+ const branches = moment.branches ?? [];
63806
+ const seq = moment.sequence;
63807
+ if (seq && isUsableSequence(seq, entries.length, branches.length)) {
63808
+ return seq.map((item) => item.kind === "entry" ? { kind: "entry", entry: entries[item.index], entryIndex: item.index } : { kind: "branch", branch: branches[item.index], branchIndex: item.index });
63809
+ }
63810
+ const entryChildren = entries.map((entry, i) => ({
63811
+ kind: "entry",
63812
+ entry,
63813
+ entryIndex: i
63814
+ }));
63815
+ const branchChildren = branches.map((branch, i) => ({
63816
+ kind: "branch",
63817
+ branch,
63818
+ branchIndex: i
63819
+ }));
63820
+ return fallback === "entries-first" ? [...entryChildren, ...branchChildren] : [...branchChildren, ...entryChildren];
63821
+ }
63822
+ function isUsableSequence(seq, entryCount, branchCount) {
63823
+ if (seq.length !== entryCount + branchCount)
63824
+ return false;
63825
+ const seenEntries = /* @__PURE__ */ new Set();
63826
+ const seenBranches = /* @__PURE__ */ new Set();
63827
+ for (const item of seq) {
63828
+ if (item.kind === "entry") {
63829
+ if (item.index < 0 || item.index >= entryCount || seenEntries.has(item.index))
63830
+ return false;
63831
+ seenEntries.add(item.index);
63832
+ } else {
63833
+ if (item.index < 0 || item.index >= branchCount || seenBranches.has(item.index))
63834
+ return false;
63835
+ seenBranches.add(item.index);
63836
+ }
63837
+ }
63838
+ return true;
63839
+ }
63840
+
63180
63841
  // ../derive/dist/engine/simulation-scenario-generator.js
63181
63842
  function resolveNodeKind(nodeName, contextId, contextMap) {
63182
63843
  const ctx = contextMap.get(contextId);
63183
63844
  if (!ctx)
63184
63845
  return "event";
63185
- const isCommand2 = findCommand(ctx, nodeName) !== void 0;
63186
- if (isCommand2)
63846
+ const isCommand = findCommand(ctx, nodeName) !== void 0;
63847
+ if (isCommand)
63187
63848
  return "command";
63188
63849
  const isEvent = findEvent(ctx, nodeName) !== void 0;
63189
63850
  if (isEvent)
@@ -63203,17 +63864,31 @@ function resolveOptions(flow, options) {
63203
63864
  function generateSimulationScenario(ir, flow, options) {
63204
63865
  const opts = resolveOptions(flow, options);
63205
63866
  const contextMap = buildContextMap(ir);
63206
- const nodes = collectOrderedNodes(flow, opts.branchSelections);
63207
- const activeBranches = collectActiveBranches(flow, opts.branchSelections);
63208
- const rawEvents = generateEvents(nodes, flow, contextMap, ir, opts.sessionId, opts.correlationId, opts.baseTime);
63867
+ const walk = walkPath(flow, opts.branchSelections);
63868
+ const nodes = walk.nodes;
63869
+ const seenActivations = /* @__PURE__ */ new Set();
63870
+ const activeBranches = [];
63871
+ for (const a of walk.activations) {
63872
+ const key = `${a.momentName}\0${a.condition}`;
63873
+ if (seenActivations.has(key))
63874
+ continue;
63875
+ seenActivations.add(key);
63876
+ activeBranches.push({
63877
+ momentName: a.momentName,
63878
+ condition: a.condition,
63879
+ routeChosen: a.condition
63880
+ });
63881
+ }
63882
+ const rawEvents = generateEvents(nodes, flow, contextMap, opts.sessionId, opts.correlationId, opts.baseTime);
63209
63883
  const events = injectSagaTransitions(rawEvents, ir, opts.sessionId, opts.correlationId);
63210
63884
  const contextNames = [...new Set(nodes.map((n) => n.contextName))];
63211
- const label = resolveScenarioLabel(flow, opts.branchSelections);
63885
+ const label = resolveScenarioLabel(flow, walk.reachedBranches);
63212
63886
  return {
63213
63887
  scenarioId: opts.scenarioId,
63214
63888
  flowId: flow.id,
63215
63889
  flowName: flow.name,
63216
63890
  scenarioLabel: label,
63891
+ kind: resolveScenarioKind(walk.reachedBranches),
63217
63892
  description: flow.description ?? `Full lifecycle flow: ${flow.name}`,
63218
63893
  given: `A ${(flow.moments[0]?.name ?? "").toLowerCase()} scenario is initiated`,
63219
63894
  when: buildWhenClause(flow, nodes),
@@ -63228,105 +63903,184 @@ function generateAllScenarios(ir, flow, options) {
63228
63903
  if (combinations.length === 0) {
63229
63904
  return [generateSimulationScenario(ir, flow, options)];
63230
63905
  }
63231
- return combinations.map((selections, idx) => generateSimulationScenario(ir, flow, {
63232
- ...options,
63233
- scenarioId: options?.scenarioId ? `${options.scenarioId}-${idx}` : `scenario-${flow.id}-${idx}`,
63234
- branchSelections: selections
63235
- }));
63906
+ const seenPaths = /* @__PURE__ */ new Set();
63907
+ const scenarios = [];
63908
+ for (const selections of combinations) {
63909
+ const pathKey = walkPath(flow, selections).nodes.map((n) => n.nodeId).join("|");
63910
+ if (seenPaths.has(pathKey))
63911
+ continue;
63912
+ seenPaths.add(pathKey);
63913
+ const idx = scenarios.length;
63914
+ scenarios.push(generateSimulationScenario(ir, flow, {
63915
+ ...options,
63916
+ scenarioId: options?.scenarioId ? `${options.scenarioId}-${idx}` : `scenario-${flow.id}-${idx}`,
63917
+ branchSelections: selections
63918
+ }));
63919
+ }
63920
+ return scenarios;
63236
63921
  }
63237
63922
  function enumerateBranchCombinations(flow) {
63238
- const branchPoints = flow.moments.filter((m) => m.branches && m.branches.length > 0);
63239
- if (branchPoints.length === 0)
63923
+ const hasBranchPoints = flow.moments.some((m) => m.branches && m.branches.length > 0);
63924
+ if (!hasBranchPoints)
63240
63925
  return [];
63241
- let combos = [{}];
63242
- for (const moment of branchPoints) {
63243
- const nextCombos = [];
63244
- for (const existing of combos) {
63245
- for (const branch of moment.branches) {
63246
- nextCombos.push({ ...existing, [moment.name]: branch.condition });
63247
- }
63926
+ const results = [];
63927
+ const expand = (selections) => {
63928
+ const walk = walkPath(flow, selections);
63929
+ const next = walk.reachedBranches.find((b) => !(b.momentName in selections));
63930
+ if (!next) {
63931
+ results.push(selections);
63932
+ return;
63248
63933
  }
63249
- combos = nextCombos;
63250
- }
63251
- return combos;
63934
+ const moment = flow.moments.find((m) => m.name === next.momentName);
63935
+ for (const branch of moment?.branches ?? []) {
63936
+ expand({ ...selections, [next.momentName]: branch.condition });
63937
+ }
63938
+ };
63939
+ expand({});
63940
+ return results;
63252
63941
  }
63253
- function resolveScenarioLabel(flow, branchSelections) {
63254
- const branchMoments = flow.moments.filter((m) => m.branches && m.branches.length > 0);
63255
- if (branchMoments.length === 0)
63256
- return `Happy Path: ${flow.name}`;
63257
- const terminalConditions = collectTerminalConditions(branchMoments, branchSelections);
63258
- const isAllFirstBranch = checkAllFirstBranch(branchMoments, branchSelections);
63259
- if (isAllFirstBranch)
63942
+ function resolveScenarioLabel(flow, reachedBranches) {
63943
+ const deviations = reachedBranches.filter((b) => !b.isFirstArm);
63944
+ if (deviations.length === 0)
63260
63945
  return `Happy Path: ${flow.name}`;
63261
- if (terminalConditions.length > 0) {
63262
- return `Failure Path: ${flow.name} [${terminalConditions.join(", ")}]`;
63263
- }
63264
- const selectedConditions = branchMoments.map((m) => branchSelections[m.name]).filter(Boolean);
63265
- return `Variant: ${flow.name} [${selectedConditions.join(", ")}]`;
63266
- }
63267
- function collectTerminalConditions(branchMoments, branchSelections) {
63268
- const conditions = [];
63269
- for (const moment of branchMoments) {
63270
- const selected = branchSelections[moment.name];
63271
- if (!selected)
63272
- continue;
63273
- const branch = moment.branches.find((b) => b.condition === selected);
63274
- if (branch && branch.entries.some((e) => e.terminal)) {
63275
- conditions.push(selected);
63276
- }
63277
- }
63278
- return conditions;
63946
+ const bracket = deviations.map((b) => b.condition).join(", ");
63947
+ const hasTerminalArm = deviations.some((b) => b.armHasTerminal);
63948
+ return hasTerminalArm ? `Failure Path: ${flow.name} [${bracket}]` : `Variant: ${flow.name} [${bracket}]`;
63279
63949
  }
63280
- function checkAllFirstBranch(branchMoments, branchSelections) {
63281
- for (const moment of branchMoments) {
63282
- const selected = branchSelections[moment.name];
63283
- const firstCondition = moment.branches[0]?.condition;
63284
- if (selected && selected !== firstCondition)
63285
- return false;
63286
- }
63287
- return true;
63950
+ function resolveScenarioKind(reachedBranches) {
63951
+ const deviations = reachedBranches.filter((b) => !b.isFirstArm);
63952
+ if (deviations.length === 0)
63953
+ return "happy";
63954
+ return deviations.some((b) => b.armHasTerminal) ? "failure" : "variant";
63288
63955
  }
63289
63956
  function buildContextMap(ir) {
63290
63957
  return new Map(ir.contexts.map((c) => [c.id, c]));
63291
63958
  }
63292
- function collectOrderedNodes(flow, branchSelections) {
63959
+ function walkPath(flow, branchSelections) {
63293
63960
  const laneIndex = new Map(flow.lanes.map((l) => [l.contextId, l.id]));
63294
- const nodes = [];
63295
- for (let i = 0; i < flow.moments.length; i++) {
63296
- const moment = flow.moments[i];
63297
- const stopped = collectMomentNodes(nodes, moment, i, branchSelections, laneIndex);
63298
- if (stopped)
63961
+ const momentIndexById = new Map(flow.moments.map((m, idx2) => [m.id, idx2]));
63962
+ const returnsToCount = flow.connections.filter((c) => c.connectionType === "returns-to").length;
63963
+ const maxMomentVisits = flow.moments.length * (returnsToCount + 2);
63964
+ const state = {
63965
+ nodes: [],
63966
+ reachedBranches: [],
63967
+ activations: [],
63968
+ reachedNames: /* @__PURE__ */ new Set(),
63969
+ forcedFirstArm: /* @__PURE__ */ new Set(),
63970
+ usedReturns: /* @__PURE__ */ new Set()
63971
+ };
63972
+ let idx = 0;
63973
+ let visits = 0;
63974
+ while (idx < flow.moments.length && visits < maxMomentVisits) {
63975
+ visits++;
63976
+ const moment = flow.moments[idx];
63977
+ const step = moment.branches && moment.branches.length > 0 ? walkBranchedMoment(moment, idx, flow, branchSelections, laneIndex, momentIndexById, state) : walkPlainMoment(moment, idx, laneIndex, state);
63978
+ if (step.stop)
63299
63979
  break;
63980
+ idx = step.jumpTo ?? idx + 1;
63300
63981
  }
63301
- return nodes;
63982
+ return {
63983
+ nodes: state.nodes,
63984
+ reachedBranches: state.reachedBranches,
63985
+ activations: state.activations
63986
+ };
63302
63987
  }
63303
- function collectMomentNodes(nodes, moment, momentIndex, branchSelections, laneIndex) {
63304
- if (moment.branches && moment.branches.length > 0) {
63305
- return collectBranchedMomentNodes(nodes, moment, momentIndex, branchSelections, laneIndex);
63306
- }
63307
- for (const entry of moment.contextEntries) {
63308
- nodes.push(entryToNode(entry, moment, momentIndex, laneIndex, "main"));
63988
+ function walkPlainMoment(moment, momentIndex, laneIndex, state) {
63989
+ for (const child of orderedMomentChildren(moment, "entries-first")) {
63990
+ if (child.kind !== "entry")
63991
+ continue;
63992
+ pushEntryNodes(state.nodes, child.entry, moment, momentIndex, laneIndex, "main", void 0);
63993
+ if (child.entry.terminal)
63994
+ return { stop: true };
63995
+ }
63996
+ return { stop: false };
63997
+ }
63998
+ function walkBranchedMoment(moment, momentIndex, flow, branchSelections, laneIndex, momentIndexById, state) {
63999
+ const branches = moment.branches;
64000
+ const { branch, forced } = selectBranch(moment, branchSelections, state);
64001
+ if (!branch)
64002
+ return walkPlainMoment(moment, momentIndex, laneIndex, state);
64003
+ const branchIdx = branches.indexOf(branch);
64004
+ recordBranchPoint(state, moment, branch, branchIdx, forced);
64005
+ let emittedAny = false;
64006
+ let branchEmitted = false;
64007
+ for (const child of orderedMomentChildren(moment, "branches-first")) {
64008
+ if (child.kind === "branch") {
64009
+ if (branchEmitted)
64010
+ continue;
64011
+ branchEmitted = true;
64012
+ const step = emitSelectedArm(moment, momentIndex, flow, branch, branchIdx, laneIndex, momentIndexById, state);
64013
+ if (step)
64014
+ return step;
64015
+ if (branch.entries.length > 0)
64016
+ emittedAny = true;
64017
+ } else {
64018
+ const outcome = emittedAny ? void 0 : branch.condition;
64019
+ pushEntryNodes(state.nodes, child.entry, moment, momentIndex, laneIndex, "main", outcome);
64020
+ emittedAny = true;
64021
+ if (child.entry.terminal)
64022
+ return { stop: true };
64023
+ }
64024
+ }
64025
+ return { stop: false };
64026
+ }
64027
+ function selectBranch(moment, branchSelections, state) {
64028
+ const branches = moment.branches;
64029
+ const forced = state.forcedFirstArm.has(moment.name);
64030
+ const selected = forced ? branches[0]?.condition : branchSelections[moment.name];
64031
+ const branch = (selected ? branches.find((b) => b.condition === selected) : void 0) ?? branches[0];
64032
+ return { branch, forced };
64033
+ }
64034
+ function recordBranchPoint(state, moment, branch, branchIdx, forced) {
64035
+ state.activations.push({ momentName: moment.name, condition: branch.condition });
64036
+ if (!forced && !state.reachedNames.has(moment.name)) {
64037
+ state.reachedNames.add(moment.name);
64038
+ state.reachedBranches.push({
64039
+ momentName: moment.name,
64040
+ condition: branch.condition,
64041
+ isFirstArm: branchIdx === 0,
64042
+ armHasTerminal: branch.entries.some((e) => e.terminal)
64043
+ });
63309
64044
  }
63310
- return false;
63311
64045
  }
63312
- function collectBranchedMomentNodes(nodes, moment, momentIndex, branchSelections, laneIndex) {
63313
- const selectedCondition = branchSelections[moment.name];
63314
- const branch = selectedCondition ? moment.branches.find((b) => b.condition === selectedCondition) : moment.branches[0];
63315
- let hitTerminal = false;
63316
- if (branch) {
63317
- const branchIdx = moment.branches.indexOf(branch);
63318
- for (const entry of branch.entries) {
63319
- nodes.push(entryToNode(entry, moment, momentIndex, laneIndex, `br${branchIdx}`));
63320
- if (entry.terminal)
63321
- hitTerminal = true;
64046
+ function emitSelectedArm(moment, momentIndex, flow, branch, branchIdx, laneIndex, momentIndexById, state) {
64047
+ for (const entry of branch.entries) {
64048
+ pushEntryNodes(state.nodes, entry, moment, momentIndex, laneIndex, `br${branchIdx}`, branch.condition);
64049
+ if (entry.terminal)
64050
+ return { stop: true };
64051
+ }
64052
+ const returnsTo = findReturnsToConnection(flow, moment, branch);
64053
+ if (returnsTo?.targetMomentId !== void 0 && !state.usedReturns.has(returnsTo.id)) {
64054
+ const targetIdx = momentIndexById.get(returnsTo.targetMomentId);
64055
+ if (targetIdx !== void 0) {
64056
+ state.usedReturns.add(returnsTo.id);
64057
+ state.forcedFirstArm.add(moment.name);
64058
+ return { stop: false, jumpTo: targetIdx };
63322
64059
  }
63323
64060
  }
63324
- for (const entry of moment.contextEntries) {
63325
- nodes.push(entryToNode(entry, moment, momentIndex, laneIndex, "main"));
64061
+ return null;
64062
+ }
64063
+ function findReturnsToConnection(flow, moment, branch) {
64064
+ return flow.connections.find((c) => c.connectionType === "returns-to" && c.sourceMomentId === moment.id && (c.branchCondition === branch.condition || c.branchCondition === void 0 && branch.entries.some((e) => e.nodeName === c.sourceNodeName)));
64065
+ }
64066
+ function pushEntryNodes(nodes, entry, moment, momentIndex, laneIndex, scope, outcome) {
64067
+ const count = resolveMultiplicity(entry.multiplicity);
64068
+ for (let k = 0; k < count; k++) {
64069
+ nodes.push(entryToNode(entry, moment, momentIndex, laneIndex, scope, outcome));
64070
+ }
64071
+ }
64072
+ function resolveMultiplicity(multiplicity) {
64073
+ if (typeof multiplicity === "number" && Number.isFinite(multiplicity) && multiplicity > 1) {
64074
+ return Math.floor(multiplicity);
64075
+ }
64076
+ if (typeof multiplicity === "string" && /^\d+$/.test(multiplicity)) {
64077
+ const n = parseInt(multiplicity, 10);
64078
+ if (n > 1)
64079
+ return n;
63326
64080
  }
63327
- return hitTerminal;
64081
+ return 1;
63328
64082
  }
63329
- function entryToNode(entry, moment, momentIndex, laneIndex, scope) {
64083
+ function entryToNode(entry, moment, momentIndex, laneIndex, scope, outcome) {
63330
64084
  const contextName = entry.contextId.replace(/^ctx-/, "");
63331
64085
  const laneId = laneIndex.get(entry.contextId) ?? entry.contextId;
63332
64086
  return {
@@ -63336,42 +64090,24 @@ function entryToNode(entry, moment, momentIndex, laneIndex, scope) {
63336
64090
  contextName,
63337
64091
  nodeKind: entry.nodeKind,
63338
64092
  momentName: moment.name,
63339
- momentIndex
64093
+ momentId: moment.id,
64094
+ momentIndex,
64095
+ ...outcome !== void 0 ? { outcome } : {}
63340
64096
  };
63341
64097
  }
63342
- function collectActiveBranches(flow, branchSelections) {
63343
- const branches = [];
63344
- for (const moment of flow.moments) {
63345
- if (moment.branches && moment.branches.length > 0) {
63346
- const selected = branchSelections[moment.name] ?? moment.branches[0]?.condition;
63347
- if (selected) {
63348
- branches.push({
63349
- momentName: moment.name,
63350
- condition: selected,
63351
- routeChosen: selected
63352
- });
63353
- }
63354
- }
63355
- }
63356
- return branches;
63357
- }
63358
- function generateEvents(nodes, flow, contextMap, ir, sessionId, correlationId, baseTime) {
64098
+ function generateEvents(nodes, flow, contextMap, sessionId, correlationId, baseTime) {
63359
64099
  const events = [];
63360
- const nodeToEventId = /* @__PURE__ */ new Map();
63361
- let eventCounter = 0;
64100
+ const eventIds = nodes.map((_, i) => `evt-${String(i + 1).padStart(3, "0")}`);
63362
64101
  for (let i = 0; i < nodes.length; i++) {
63363
64102
  const node = nodes[i];
63364
- eventCounter++;
63365
- const eventId = `evt-${String(eventCounter).padStart(3, "0")}`;
63366
- nodeToEventId.set(node.nodeId, eventId);
63367
64103
  const timestamp = new Date(baseTime.getTime() + i * 1e3).toISOString();
63368
- const causationEventIds = findCausationEvents(node, nodes, flow, nodeToEventId, i);
64104
+ const causationEventIds = findCausationEvents(node, nodes, flow, eventIds, i);
63369
64105
  const effectiveKind = resolveNodeKind(node.nodeName, node.contextId, contextMap);
63370
64106
  const eventType = resolveEventType(node, effectiveKind);
63371
64107
  const productSource = resolveProductSource(node, effectiveKind, contextMap);
63372
- const payload = buildPayload(node, effectiveKind, contextMap, ir);
64108
+ const payload = buildPayload(node, effectiveKind, contextMap, flow);
63373
64109
  events.push({
63374
- eventId,
64110
+ eventId: eventIds[i],
63375
64111
  eventType,
63376
64112
  productSource,
63377
64113
  sessionId,
@@ -63384,34 +64120,40 @@ function generateEvents(nodes, flow, contextMap, ir, sessionId, correlationId, b
63384
64120
  }
63385
64121
  return events;
63386
64122
  }
63387
- function findCausationEvents(node, allNodes, flow, nodeToEventId, currentIndex) {
64123
+ function findCausationEvents(node, allNodes, flow, eventIds, currentIndex) {
63388
64124
  const causation = [];
63389
64125
  for (const conn of flow.connections) {
63390
- if (conn.connectionType === "triggered-by") {
63391
- addTriggeredByCausation(conn, allNodes, nodeToEventId, currentIndex, causation);
63392
- }
64126
+ if (!isCausationConnectionFor(conn, node))
64127
+ continue;
64128
+ const triggerName = conn.sourceNodeName ?? conn.eventId.replace(/^evt-/, "");
64129
+ const causeId = findLatestPriorEmission(triggerName, allNodes, eventIds, currentIndex);
64130
+ if (causeId !== void 0)
64131
+ causation.push(causeId);
63393
64132
  }
63394
- if (causation.length === 0 && currentIndex > 0) {
63395
- const prevNode = allNodes[currentIndex - 1];
63396
- const prevEvtId = nodeToEventId.get(prevNode.nodeId);
63397
- if (prevEvtId)
63398
- causation.push(prevEvtId);
64133
+ const deduped = [...new Set(causation)];
64134
+ if (deduped.length === 0 && currentIndex > 0) {
64135
+ deduped.push(eventIds[currentIndex - 1]);
63399
64136
  }
63400
- return causation;
64137
+ return deduped;
63401
64138
  }
63402
- function addTriggeredByCausation(conn, allNodes, nodeToEventId, currentIndex, causation) {
63403
- const triggerNodeName = conn.eventId.replace(/^evt-/, "");
63404
- const currentNodeInMoment = allNodes.some((n, idx) => idx === currentIndex && n.nodeName === triggerNodeName);
63405
- if (currentNodeInMoment)
63406
- return;
64139
+ function isCausationConnectionFor(conn, node) {
64140
+ if (conn.connectionType !== "triggered-by")
64141
+ return false;
64142
+ if (conn.targetNodeName !== node.nodeName)
64143
+ return false;
64144
+ if (conn.sourceMomentId !== node.momentId)
64145
+ return false;
64146
+ if (conn.branchCondition !== void 0 && conn.branchCondition !== node.outcome)
64147
+ return false;
64148
+ return true;
64149
+ }
64150
+ function findLatestPriorEmission(triggerName, allNodes, eventIds, currentIndex) {
63407
64151
  for (let j = currentIndex - 1; j >= 0; j--) {
63408
- if (allNodes[j].nodeName === triggerNodeName) {
63409
- const evtId = nodeToEventId.get(allNodes[j].nodeId);
63410
- if (evtId)
63411
- causation.push(evtId);
63412
- break;
64152
+ if (allNodes[j].nodeName === triggerName) {
64153
+ return eventIds[j];
63413
64154
  }
63414
64155
  }
64156
+ return void 0;
63415
64157
  }
63416
64158
  function resolveEventType(node, effectiveKind) {
63417
64159
  if (effectiveKind === "command") {
@@ -63431,99 +64173,112 @@ function resolveProductSource(node, effectiveKind, contextMap) {
63431
64173
  }
63432
64174
  return "moment:simulation";
63433
64175
  }
63434
- function buildPayload(node, effectiveKind, contextMap, ir) {
64176
+ function buildPayload(node, effectiveKind, contextMap, flow) {
63435
64177
  const payload = {
63436
64178
  nodeId: node.nodeId
63437
64179
  };
63438
64180
  const ctx = contextMap.get(node.contextId);
63439
- if (!ctx)
63440
- return payload;
63441
- if (effectiveKind === "command") {
63442
- return buildCommandPayload(payload, ctx, node);
63443
- }
63444
- if (effectiveKind === "event") {
63445
- return buildEventPayload(payload, ctx, node);
63446
- }
63447
- return payload;
63448
- }
63449
- function buildCommandPayload(payload, ctx, node) {
63450
- payload.processName = node.nodeName;
63451
- const cmd = findCommand(ctx, node.nodeName);
64181
+ const cmd = ctx && effectiveKind === "command" ? findCommand(ctx, node.nodeName) : void 0;
64182
+ const evt = ctx && effectiveKind === "event" ? findEvent(ctx, node.nodeName) : void 0;
63452
64183
  if (cmd) {
64184
+ payload.processName = node.nodeName;
63453
64185
  for (const input of cmd.inputs) {
63454
64186
  payload[input.name] = generatePlaceholder(input.type, input.name);
63455
64187
  }
64188
+ } else if (evt) {
64189
+ for (const field of evt.fields) {
64190
+ payload[field.name] = generatePlaceholder(field.type, field.name);
64191
+ }
64192
+ }
64193
+ addContractFields(payload, node, flow);
64194
+ if (node.outcome !== void 0) {
64195
+ payload.outcome = node.outcome;
63456
64196
  }
63457
64197
  return payload;
63458
64198
  }
63459
- function buildEventPayload(payload, ctx, node) {
63460
- const evt = findEvent(ctx, node.nodeName);
63461
- if (evt) {
63462
- for (const field of evt.fields) {
64199
+ function addContractFields(payload, node, flow) {
64200
+ for (const conn of flow.connections) {
64201
+ if (conn.connectionType !== "crosses-to")
64202
+ continue;
64203
+ if (conn.sourceNodeName !== node.nodeName)
64204
+ continue;
64205
+ if (!("schemaContract" in conn))
64206
+ continue;
64207
+ for (const field of conn.schemaContract.fields) {
64208
+ if (field.name in payload)
64209
+ continue;
63463
64210
  payload[field.name] = generatePlaceholder(field.type, field.name);
63464
64211
  }
63465
64212
  }
63466
- return payload;
63467
64213
  }
63468
64214
  function injectSagaTransitions(events, ir, sessionId, correlationId) {
63469
- const sagaIndex = buildSagaIndex(ir);
63470
- if (sagaIndex.size === 0)
64215
+ const sagas = collectSagas(ir);
64216
+ if (sagas.length === 0 || events.length === 0)
63471
64217
  return events;
63472
- const result = [];
63473
- const sagaProgress = /* @__PURE__ */ new Map();
63474
- let eventCounter = events.length;
63475
- for (const event of events) {
63476
- result.push(event);
63477
- const matchingSagas = sagaIndex.get(event.eventType);
63478
- if (!matchingSagas)
63479
- continue;
63480
- for (const saga of matchingSagas) {
63481
- eventCounter++;
63482
- const sagaEvent = buildSagaEvent(saga, sagaProgress, event, eventCounter, sessionId, correlationId);
63483
- if (sagaEvent)
63484
- result.push(sagaEvent);
64218
+ const sagaEvents = [];
64219
+ const lastPathTime = new Date(events[events.length - 1].timestamp).getTime();
64220
+ for (const saga of sagas) {
64221
+ for (const step of computeSagaProgression(saga, events)) {
64222
+ const eventCounter = events.length + sagaEvents.length + 1;
64223
+ const timestamp = new Date(lastPathTime + (sagaEvents.length + 1) * 1e3).toISOString();
64224
+ sagaEvents.push({
64225
+ eventId: `evt-${String(eventCounter).padStart(3, "0")}`,
64226
+ eventType: `SimSaga.${saga.name}.${step.state}`,
64227
+ productSource: "moment:simulation",
64228
+ sessionId,
64229
+ causationEventIds: [step.cause.eventId],
64230
+ correlationId,
64231
+ timestamp,
64232
+ version: 1,
64233
+ payload: {
64234
+ sagaName: saga.name,
64235
+ state: step.state,
64236
+ triggeredBy: step.cause.eventType
64237
+ }
64238
+ });
63485
64239
  }
63486
64240
  }
63487
- return result;
64241
+ return [...events, ...sagaEvents];
63488
64242
  }
63489
- function buildSagaIndex(ir) {
63490
- const index = /* @__PURE__ */ new Map();
63491
- for (const ctx of ir.contexts) {
63492
- for (const saga of ctx.sagas) {
63493
- const existing = index.get(saga.trigger) ?? [];
63494
- existing.push(saga);
63495
- index.set(saga.trigger, existing);
63496
- }
64243
+ function collectSagas(ir) {
64244
+ return ir.contexts.flatMap((ctx) => ctx.sagas);
64245
+ }
64246
+ function computeSagaProgression(saga, events) {
64247
+ if (saga.states.length === 0)
64248
+ return [];
64249
+ const triggerIdx = events.findIndex((e) => e.eventType === saga.trigger);
64250
+ if (triggerIdx === -1)
64251
+ return [];
64252
+ const steps = [{ state: saga.states[0], cause: events[triggerIdx] }];
64253
+ let cursor = triggerIdx;
64254
+ for (const transition of sagaTransitionChain(saga)) {
64255
+ if (!transition.onEvent)
64256
+ break;
64257
+ const fireIdx = findEventAfter(events, transition.onEvent, cursor);
64258
+ if (fireIdx === -1)
64259
+ break;
64260
+ steps.push({ state: transition.to, cause: events[fireIdx] });
64261
+ cursor = fireIdx;
63497
64262
  }
63498
- return index;
64263
+ return steps;
63499
64264
  }
63500
- function buildSagaEvent(saga, sagaProgress, triggerEvent, eventCounter, sessionId, correlationId) {
63501
- const currentIdx = sagaProgress.get(saga.name) ?? 0;
63502
- if (currentIdx >= saga.states.length)
63503
- return null;
63504
- const nextState = saga.states[currentIdx];
63505
- sagaProgress.set(saga.name, currentIdx + 1);
63506
- return {
63507
- eventId: `evt-${String(eventCounter).padStart(3, "0")}`,
63508
- eventType: `SimSaga.${saga.name}.${nextState}`,
63509
- productSource: "moment:simulation",
63510
- sessionId,
63511
- causationEventIds: [triggerEvent.eventId],
63512
- correlationId,
63513
- timestamp: triggerEvent.timestamp,
63514
- version: 1,
63515
- payload: {
63516
- sagaName: saga.name,
63517
- state: nextState,
63518
- triggeredBy: triggerEvent.eventType
63519
- }
63520
- };
64265
+ function sagaTransitionChain(saga) {
64266
+ return saga.transitions ?? [];
64267
+ }
64268
+ function findEventAfter(events, eventType, afterIndex) {
64269
+ for (let i = afterIndex + 1; i < events.length; i++) {
64270
+ if (events[i].eventType === eventType)
64271
+ return i;
64272
+ }
64273
+ return -1;
63521
64274
  }
63522
64275
  function deriveNegativeScenarios(ir, flow, options) {
63523
64276
  const contextMap = buildContextMap(ir);
63524
64277
  const opts = resolveOptions(flow, options);
63525
- const nodes = collectOrderedNodes(flow, opts.branchSelections);
64278
+ const walk = walkPath(flow, opts.branchSelections);
64279
+ const nodes = walk.nodes;
63526
64280
  const scenarios = [];
64281
+ const usedIds = /* @__PURE__ */ new Set();
63527
64282
  for (let i = 0; i < nodes.length; i++) {
63528
64283
  const node = nodes[i];
63529
64284
  const kind = resolveNodeKind(node.nodeName, node.contextId, contextMap);
@@ -63531,7 +64286,11 @@ function deriveNegativeScenarios(ir, flow, options) {
63531
64286
  continue;
63532
64287
  const preconditions = findPreconditions(node, contextMap);
63533
64288
  for (const pre of preconditions) {
63534
- scenarios.push(buildNegativeScenario(ir, flow, nodes, i, pre, contextMap, opts));
64289
+ const baseId = `${opts.scenarioId}-neg-${node.nodeName}-${pre.name}`;
64290
+ const scenarioId = usedIds.has(baseId) ? `${baseId}-${i}` : baseId;
64291
+ usedIds.add(scenarioId);
64292
+ usedIds.add(baseId);
64293
+ scenarios.push(buildNegativeScenario(ir, flow, walk, i, pre, contextMap, opts, scenarioId));
63535
64294
  }
63536
64295
  }
63537
64296
  return scenarios;
@@ -63543,23 +64302,41 @@ function findPreconditions(node, contextMap) {
63543
64302
  const cmd = findCommand(ctx, node.nodeName);
63544
64303
  return cmd?.preconditions ?? [];
63545
64304
  }
63546
- function buildNegativeScenario(ir, flow, allNodes, failIndex, precondition, contextMap, opts) {
64305
+ function buildNegativeScenario(ir, flow, walk, failIndex, precondition, contextMap, opts, scenarioId) {
64306
+ const allNodes = walk.nodes;
63547
64307
  const precedingNodes = allNodes.slice(0, failIndex + 1);
63548
64308
  const failNode = allNodes[failIndex];
63549
- const events = generateEvents(precedingNodes, flow, contextMap, ir, opts.sessionId, opts.correlationId, opts.baseTime);
64309
+ const reachedMoments = new Set(precedingNodes.map((n) => n.momentName));
64310
+ const seenActivations = /* @__PURE__ */ new Set();
64311
+ const activeBranches = [];
64312
+ for (const a of walk.activations) {
64313
+ if (!reachedMoments.has(a.momentName))
64314
+ continue;
64315
+ const key = `${a.momentName} ${a.condition}`;
64316
+ if (seenActivations.has(key))
64317
+ continue;
64318
+ seenActivations.add(key);
64319
+ activeBranches.push({
64320
+ momentName: a.momentName,
64321
+ condition: a.condition,
64322
+ routeChosen: a.condition
64323
+ });
64324
+ }
64325
+ const events = generateEvents(precedingNodes, flow, contextMap, opts.sessionId, opts.correlationId, opts.baseTime);
63550
64326
  const failureEvent = buildFailureEvent(failNode, precondition, events, opts);
63551
- const allEvents = [...events, failureEvent];
64327
+ const allEvents = injectSagaTransitions([...events, failureEvent], ir, opts.sessionId, opts.correlationId);
63552
64328
  return {
63553
- scenarioId: `${opts.scenarioId}-neg-${failNode.nodeName}-${precondition.name}`,
64329
+ scenarioId,
63554
64330
  flowId: flow.id,
63555
64331
  flowName: flow.name,
63556
64332
  scenarioLabel: `Failure: ${precondition.description} not met`,
64333
+ kind: "negative",
63557
64334
  description: `Negative scenario: ${precondition.description} precondition violation at ${failNode.nodeName}`,
63558
64335
  given: `A ${(flow.moments[0]?.name ?? "").toLowerCase()} scenario is initiated`,
63559
64336
  when: `${failNode.nodeName} is invoked without satisfying ${precondition.name}`,
63560
64337
  then: `The command ${failNode.nodeName} is rejected`,
63561
64338
  expectedPath: precedingNodes.map((n) => n.nodeId),
63562
- activeBranches: [],
64339
+ activeBranches,
63563
64340
  events: allEvents
63564
64341
  };
63565
64342
  }
@@ -63582,6 +64359,111 @@ function buildFailureEvent(failNode, precondition, precedingEvents, opts) {
63582
64359
  }
63583
64360
  };
63584
64361
  }
64362
+ function deriveTimeoutScenarios(ir, flow, options) {
64363
+ const opts = resolveOptions(flow, options);
64364
+ const contextMap = buildContextMap(ir);
64365
+ const scenarios = [];
64366
+ for (const saga of collectSagas(ir)) {
64367
+ if (saga.timeout === "none" || saga.timeout === "")
64368
+ continue;
64369
+ if (saga.states.length === 0)
64370
+ continue;
64371
+ const owner = ir.flows.find((f) => flowContainsNodeName(f, saga.trigger)) ?? ir.flows[0];
64372
+ if (!owner || owner.id !== flow.id)
64373
+ continue;
64374
+ const scenario = buildTimeoutScenario(flow, saga, contextMap, opts);
64375
+ if (scenario)
64376
+ scenarios.push(scenario);
64377
+ }
64378
+ return scenarios;
64379
+ }
64380
+ function flowContainsNodeName(flow, nodeName) {
64381
+ for (const moment of flow.moments) {
64382
+ if (moment.contextEntries.some((e) => e.nodeName === nodeName))
64383
+ return true;
64384
+ for (const branch of moment.branches ?? []) {
64385
+ if (branch.entries.some((e) => e.nodeName === nodeName))
64386
+ return true;
64387
+ }
64388
+ }
64389
+ return false;
64390
+ }
64391
+ function buildTimeoutScenario(flow, saga, contextMap, opts) {
64392
+ const walk = walkPath(flow, {});
64393
+ const triggerIdx = walk.nodes.findIndex((n) => n.nodeName === saga.trigger);
64394
+ if (triggerIdx === -1)
64395
+ return null;
64396
+ const nodes = truncateAfterTriggerMoment(walk.nodes, triggerIdx);
64397
+ const pathEvents = generateEvents(nodes, flow, contextMap, opts.sessionId, opts.correlationId, opts.baseTime);
64398
+ const events = appendTimeoutMarkers(pathEvents, saga, triggerIdx, opts);
64399
+ const activeBranches = collectTruncatedActiveBranches(walk.activations, nodes);
64400
+ const hasCompensation = saga.compensation !== "none" && saga.compensation !== "";
64401
+ const lastMomentName = nodes[nodes.length - 1]?.momentName ?? flow.name;
64402
+ return {
64403
+ scenarioId: `${opts.scenarioId}-timeout-${saga.name}`,
64404
+ flowId: flow.id,
64405
+ flowName: flow.name,
64406
+ scenarioLabel: `Timeout: ${flow.name} [${saga.name}.${saga.timeout}]`,
64407
+ kind: "timeout",
64408
+ description: `Timeout scenario: saga ${saga.name} timeout ${saga.timeout} elapses after ${saga.trigger}; the domain flow stops at ${lastMomentName}`,
64409
+ given: `A ${(flow.moments[0]?.name ?? "").toLowerCase()} scenario is initiated`,
64410
+ when: `Saga ${saga.name} timeout ${saga.timeout} elapses after ${saga.trigger}`,
64411
+ then: hasCompensation ? `Saga ${saga.name} times out and compensates: ${saga.compensation}` : `Saga ${saga.name} times out`,
64412
+ expectedPath: nodes.map((n) => n.nodeId),
64413
+ activeBranches,
64414
+ events
64415
+ };
64416
+ }
64417
+ function truncateAfterTriggerMoment(walkNodes, triggerIdx) {
64418
+ let cut = triggerIdx;
64419
+ while (cut + 1 < walkNodes.length && walkNodes[cut + 1].momentId === walkNodes[triggerIdx].momentId) {
64420
+ cut++;
64421
+ }
64422
+ return walkNodes.slice(0, cut + 1);
64423
+ }
64424
+ function collectTruncatedActiveBranches(activations, nodes) {
64425
+ const includedMoments = new Set(nodes.map((n) => n.momentName));
64426
+ const seenActivations = /* @__PURE__ */ new Set();
64427
+ const activeBranches = [];
64428
+ for (const a of activations) {
64429
+ if (!includedMoments.has(a.momentName))
64430
+ continue;
64431
+ const key = `${a.momentName} ${a.condition}`;
64432
+ if (seenActivations.has(key))
64433
+ continue;
64434
+ seenActivations.add(key);
64435
+ activeBranches.push({ momentName: a.momentName, condition: a.condition, routeChosen: a.condition });
64436
+ }
64437
+ return activeBranches;
64438
+ }
64439
+ function appendTimeoutMarkers(pathEvents, saga, triggerIdx, opts) {
64440
+ const lastPathTime = pathEvents.length > 0 ? new Date(pathEvents[pathEvents.length - 1].timestamp).getTime() : opts.baseTime.getTime();
64441
+ const markers = [];
64442
+ const push = (eventType, payload, causeEventId) => {
64443
+ const counter = pathEvents.length + markers.length + 1;
64444
+ const marker = {
64445
+ eventId: `evt-${String(counter).padStart(3, "0")}`,
64446
+ eventType,
64447
+ productSource: "moment:simulation",
64448
+ sessionId: opts.sessionId,
64449
+ causationEventIds: causeEventId ? [causeEventId] : [],
64450
+ correlationId: opts.correlationId,
64451
+ timestamp: new Date(lastPathTime + (markers.length + 1) * 1e3).toISOString(),
64452
+ version: 1,
64453
+ payload
64454
+ };
64455
+ markers.push(marker);
64456
+ return marker;
64457
+ };
64458
+ const triggerEvent = pathEvents[triggerIdx];
64459
+ const initialState = saga.states[0];
64460
+ const initial = push(`SimSaga.${saga.name}.${initialState}`, { sagaName: saga.name, state: initialState, triggeredBy: triggerEvent?.eventType ?? saga.trigger }, triggerEvent?.eventId);
64461
+ const timedOut = push(`SimSaga.${saga.name}.TimedOut`, { timeoutName: saga.timeout }, initial.eventId);
64462
+ if (saga.compensation !== "none" && saga.compensation !== "") {
64463
+ push(`SimSaga.${saga.name}.Compensated`, { description: saga.compensation }, timedOut.eventId);
64464
+ }
64465
+ return [...pathEvents, ...markers];
64466
+ }
63585
64467
  function findCommand(ctx, name) {
63586
64468
  const topLevel = ctx.commands.find((c) => c.name === name);
63587
64469
  if (topLevel)
@@ -63632,6 +64514,30 @@ function buildThenClause(flow, nodes, contextNames) {
63632
64514
  return `The flow completes at ${lastNode?.momentName ?? "end"} across ${contextNames.join(", ")}`;
63633
64515
  }
63634
64516
 
64517
+ // ../derive/dist/engine/policy-definitions-generator.js
64518
+ function generatePolicyDefinitions(ir) {
64519
+ const definitions = [];
64520
+ for (const context of ir.contexts) {
64521
+ for (const policy of context.policies) {
64522
+ definitions.push({
64523
+ name: policy.name,
64524
+ contextId: context.id,
64525
+ trigger: policy.trigger,
64526
+ ...policy.action ? { actionDescription: policy.action } : {},
64527
+ chainsTo: normalizeChainsTo(policy.chainsTo)
64528
+ });
64529
+ }
64530
+ }
64531
+ return definitions;
64532
+ }
64533
+ function normalizeChainsTo(chainsTo) {
64534
+ if (chainsTo === void 0 || chainsTo === null)
64535
+ return [];
64536
+ if (Array.isArray(chainsTo))
64537
+ return chainsTo.filter((c) => typeof c === "string");
64538
+ return [chainsTo];
64539
+ }
64540
+
63635
64541
  // ../derive/dist/engine/event-catalog-generator.js
63636
64542
  function generateEventCatalog(ir) {
63637
64543
  const entries = [];
@@ -63643,6 +64549,7 @@ function generateEventCatalog(ir) {
63643
64549
  }
63644
64550
  }
63645
64551
  }
64552
+ addFlowFallbackEntries(ir, entries);
63646
64553
  return {
63647
64554
  events: entries,
63648
64555
  metadata: {
@@ -63651,12 +64558,94 @@ function generateEventCatalog(ir) {
63651
64558
  }
63652
64559
  };
63653
64560
  }
64561
+ function allMomentEntries(flow) {
64562
+ const out = [];
64563
+ for (let i = 0; i < flow.moments.length; i++) {
64564
+ const moment = flow.moments[i];
64565
+ for (const entry of entriesInSequenceOrder(moment)) {
64566
+ out.push({ entry, momentName: moment.name, momentIndex: i });
64567
+ }
64568
+ }
64569
+ return out;
64570
+ }
64571
+ function entriesInSequenceOrder(moment) {
64572
+ const entries = [];
64573
+ for (const child of orderedMomentChildren(moment, "entries-first")) {
64574
+ if (child.kind === "entry") {
64575
+ entries.push(child.entry);
64576
+ } else {
64577
+ entries.push(...child.branch.entries);
64578
+ }
64579
+ }
64580
+ return entries;
64581
+ }
64582
+ function contextDisplayName(contextId, ir) {
64583
+ const ctx = ir.contexts.find((c) => c.id === contextId);
64584
+ return ctx?.name ?? contextId.replace(/^ctx-/, "");
64585
+ }
64586
+ function addFlowFallbackEntries(ir, entries) {
64587
+ const known = new Set(entries.map((e) => e.eventName));
64588
+ for (const flow of ir.flows) {
64589
+ for (const { entry, momentName, momentIndex } of allMomentEntries(flow)) {
64590
+ if (known.has(entry.nodeName))
64591
+ continue;
64592
+ known.add(entry.nodeName);
64593
+ entries.push(buildFlowEntry(entry, momentName, momentIndex, ir));
64594
+ }
64595
+ }
64596
+ }
64597
+ function buildFlowEntry(entry, momentName, momentIndex, ir) {
64598
+ const contractFields = [];
64599
+ const consumers = [];
64600
+ const seenConsumers = /* @__PURE__ */ new Set();
64601
+ let producerCommand = "";
64602
+ for (const flow of ir.flows) {
64603
+ for (const conn of flow.connections) {
64604
+ const connEventName = conn.eventId.replace(/^evt-/, "");
64605
+ if (conn.connectionType === "crosses-to" && connEventName === entry.nodeName) {
64606
+ recordCrossingConsumer(conn, ir, contractFields, consumers, seenConsumers);
64607
+ }
64608
+ if (conn.connectionType === "triggers" && conn.targetNodeName === entry.nodeName && conn.sourceNodeName && producerCommand === "") {
64609
+ producerCommand = conn.sourceNodeName;
64610
+ }
64611
+ }
64612
+ }
64613
+ return {
64614
+ eventName: entry.nodeName,
64615
+ kind: entry.nodeKind,
64616
+ schema: { fields: contractFields },
64617
+ producer: {
64618
+ context: contextDisplayName(entry.contextId, ir),
64619
+ aggregate: "",
64620
+ command: producerCommand
64621
+ },
64622
+ consumers,
64623
+ temporalLocation: { moment: momentName, momentIndex }
64624
+ };
64625
+ }
64626
+ function recordCrossingConsumer(conn, ir, contractFields, consumers, seenConsumers) {
64627
+ if (contractFields.length === 0) {
64628
+ for (const f of conn.schemaContract.fields) {
64629
+ contractFields.push({ name: f.name, type: f.type, isArray: false });
64630
+ }
64631
+ }
64632
+ const consumerName = contextDisplayName(conn.targetContextId, ir);
64633
+ const key = `${consumerName}|${conn.schemaContract.relationshipType}`;
64634
+ if (!seenConsumers.has(key)) {
64635
+ seenConsumers.add(key);
64636
+ consumers.push({
64637
+ context: consumerName,
64638
+ relationshipType: conn.schemaContract.relationshipType
64639
+ });
64640
+ }
64641
+ }
63654
64642
  function buildCatalogEntry(event, aggregate, context, ir) {
63655
64643
  const producer = findProducerCommand(event, aggregate, context);
63656
64644
  const consumers = findConsumers(event, ir);
63657
64645
  const temporalLocation = findTemporalLocation(event, ir);
63658
64646
  return {
63659
64647
  eventName: event.name,
64648
+ kind: "event",
63660
64649
  schema: {
63661
64650
  fields: event.fields.map((f) => ({
63662
64651
  name: f.name,
@@ -63690,13 +64679,14 @@ function findConsumers(event, ir) {
63690
64679
  const targetCtx = ir.contexts.find((c) => c.id === conn.targetContextId);
63691
64680
  if (!targetCtx)
63692
64681
  continue;
63693
- const key = `${targetCtx.name}|${conn.connectionType}`;
64682
+ const relationshipType = conn.schemaContract.relationshipType;
64683
+ const key = `${targetCtx.name}|${relationshipType}`;
63694
64684
  if (seen.has(key))
63695
64685
  continue;
63696
64686
  seen.add(key);
63697
64687
  consumers.push({
63698
64688
  context: targetCtx.name,
63699
- relationshipType: conn.connectionType
64689
+ relationshipType
63700
64690
  });
63701
64691
  }
63702
64692
  }
@@ -63706,7 +64696,7 @@ function findTemporalLocation(event, ir) {
63706
64696
  for (const flow of ir.flows) {
63707
64697
  for (let i = 0; i < flow.moments.length; i++) {
63708
64698
  const moment = flow.moments[i];
63709
- for (const entry of moment.contextEntries) {
64699
+ for (const entry of entriesInSequenceOrder(moment)) {
63710
64700
  if (entry.nodeKind === "event" && entry.nodeName === event.name) {
63711
64701
  return { moment: moment.name, momentIndex: i };
63712
64702
  }
@@ -63730,9 +64720,10 @@ function generateImpactAnalysis(ir) {
63730
64720
  for (const context of ir.contexts) {
63731
64721
  addCommandNodes(context, nodeMap);
63732
64722
  addEventNodes(context, nodeMap);
63733
- addPolicyNodes(context, nodeMap);
63734
- addSagaNodes(context, nodeMap);
64723
+ addPolicyNodes(context, nodeMap, ir);
64724
+ addSagaNodes(context, nodeMap, ir);
63735
64725
  }
64726
+ addFlowNodes(ir, nodeMap);
63736
64727
  addCrossingNodes(ir, nodeMap);
63737
64728
  const nodes = Array.from(nodeMap.values()).map((n) => ({
63738
64729
  type: n.type,
@@ -63777,59 +64768,145 @@ function addEventNodes(context, nodeMap) {
63777
64768
  }
63778
64769
  }
63779
64770
  }
63780
- function addPolicyNodes(context, nodeMap) {
64771
+ function findDeclaringContextForEvent(eventName, ir) {
64772
+ for (const ctx of ir.contexts) {
64773
+ if (ctx.events.some((e) => e.name === eventName))
64774
+ return ctx;
64775
+ for (const agg of ctx.aggregates) {
64776
+ if (agg.events.some((e) => e.name === eventName))
64777
+ return ctx;
64778
+ }
64779
+ }
64780
+ return void 0;
64781
+ }
64782
+ function findDeclaringContextForCommand(commandName, ir) {
64783
+ for (const ctx of ir.contexts) {
64784
+ if (ctx.commands.some((c) => c.name === commandName))
64785
+ return ctx;
64786
+ for (const agg of ctx.aggregates) {
64787
+ if (agg.commands.some((c) => c.name === commandName))
64788
+ return ctx;
64789
+ }
64790
+ }
64791
+ return void 0;
64792
+ }
64793
+ function addPolicyNodes(context, nodeMap, ir) {
63781
64794
  for (const policy of context.policies) {
63782
64795
  const policyNode = getOrCreate(nodeMap, "policy", policy.name, context.name);
63783
64796
  if (policy.trigger) {
63784
- const evtNode = getOrCreate(nodeMap, "event", policy.trigger, context.name);
63785
- policyNode.dependsOn.push(nodeKey("event", policy.trigger, context.name));
64797
+ const evtCtxName = (findDeclaringContextForEvent(policy.trigger, ir) ?? context).name;
64798
+ const evtNode = getOrCreate(nodeMap, "event", policy.trigger, evtCtxName);
64799
+ policyNode.dependsOn.push(nodeKey("event", policy.trigger, evtCtxName));
63786
64800
  evtNode.triggers.push(nodeKey("policy", policy.name, context.name));
63787
64801
  }
63788
64802
  if (policy.chainsTo) {
63789
- const cmdNode = getOrCreate(nodeMap, "command", policy.chainsTo, context.name);
63790
- policyNode.triggers.push(nodeKey("command", policy.chainsTo, context.name));
64803
+ const cmdCtxName = (findDeclaringContextForCommand(policy.chainsTo, ir) ?? context).name;
64804
+ const cmdNode = getOrCreate(nodeMap, "command", policy.chainsTo, cmdCtxName);
64805
+ policyNode.triggers.push(nodeKey("command", policy.chainsTo, cmdCtxName));
63791
64806
  cmdNode.dependsOn.push(nodeKey("policy", policy.name, context.name));
63792
64807
  }
63793
64808
  }
63794
64809
  }
63795
- function addSagaNodes(context, nodeMap) {
64810
+ function addSagaNodes(context, nodeMap, ir) {
63796
64811
  for (const saga of context.sagas) {
63797
64812
  const sagaNode = getOrCreate(nodeMap, "saga", saga.name, context.name);
63798
64813
  if (saga.trigger) {
63799
- const evtNode = getOrCreate(nodeMap, "event", saga.trigger, context.name);
63800
- sagaNode.dependsOn.push(nodeKey("event", saga.trigger, context.name));
64814
+ const evtCtxName = (findDeclaringContextForEvent(saga.trigger, ir) ?? context).name;
64815
+ const evtNode = getOrCreate(nodeMap, "event", saga.trigger, evtCtxName);
64816
+ sagaNode.dependsOn.push(nodeKey("event", saga.trigger, evtCtxName));
63801
64817
  evtNode.triggers.push(nodeKey("saga", saga.name, context.name));
63802
64818
  }
63803
64819
  }
63804
64820
  }
64821
+ function contextDisplayName2(contextId, ir) {
64822
+ const ctx = ir.contexts.find((c) => c.id === contextId);
64823
+ return ctx?.name ?? contextId.replace(/^ctx-/, "");
64824
+ }
64825
+ function addFlowNodes(ir, nodeMap) {
64826
+ for (const flow of ir.flows) {
64827
+ addFlowMomentNodes(flow, nodeMap, ir);
64828
+ addFlowTriggerEdges(flow, nodeMap);
64829
+ }
64830
+ }
64831
+ function addFlowMomentNodes(flow, nodeMap, ir) {
64832
+ for (const moment of flow.moments) {
64833
+ const entries = [
64834
+ ...moment.contextEntries,
64835
+ ...(moment.branches ?? []).flatMap((b) => b.entries)
64836
+ ];
64837
+ for (const entry of entries) {
64838
+ getOrCreate(nodeMap, entry.nodeKind, entry.nodeName, contextDisplayName2(entry.contextId, ir));
64839
+ }
64840
+ }
64841
+ }
64842
+ function addFlowTriggerEdges(flow, nodeMap) {
64843
+ for (const conn of flow.connections) {
64844
+ if (conn.connectionType !== "triggers" && conn.connectionType !== "triggered-by")
64845
+ continue;
64846
+ if (!conn.sourceNodeName || !conn.targetNodeName)
64847
+ continue;
64848
+ const cause = findNodeByName(nodeMap, conn.sourceNodeName);
64849
+ const effect = findNodeByName(nodeMap, conn.targetNodeName);
64850
+ if (!cause || !effect)
64851
+ continue;
64852
+ const causeKey = nodeKey(cause.type, cause.name, cause.context);
64853
+ const effectKey = nodeKey(effect.type, effect.name, effect.context);
64854
+ if (!cause.triggers.includes(effectKey))
64855
+ cause.triggers.push(effectKey);
64856
+ if (!effect.dependsOn.includes(causeKey))
64857
+ effect.dependsOn.push(causeKey);
64858
+ }
64859
+ }
64860
+ function findNodeByName(nodeMap, name) {
64861
+ for (const node of nodeMap.values()) {
64862
+ if (node.name === name)
64863
+ return node;
64864
+ }
64865
+ return void 0;
64866
+ }
63805
64867
  function addCrossingNodes(ir, nodeMap) {
63806
64868
  for (const flow of ir.flows) {
63807
64869
  for (const conn of flow.connections) {
63808
64870
  if (conn.connectionType !== "crosses-to")
63809
64871
  continue;
63810
- const sourceCtx = findSourceContextForEvent(conn.eventId, ir);
63811
- const targetCtx = ir.contexts.find((c) => c.id === conn.targetContextId);
63812
- if (!sourceCtx || !targetCtx)
63813
- continue;
63814
- const eventName = resolveEventName(conn.eventId, ir);
63815
- if (!eventName)
63816
- continue;
63817
- const crossingName = `${eventName}->${targetCtx.name}`;
63818
- const crossingNode = getOrCreate(nodeMap, "crossing", crossingName, sourceCtx.name);
63819
- const evtKey = nodeKey("event", eventName, sourceCtx.name);
63820
- if (!crossingNode.dependsOn.includes(evtKey)) {
63821
- crossingNode.dependsOn.push(evtKey);
63822
- }
63823
- const evtNode = nodeMap.get(evtKey);
63824
- if (evtNode) {
63825
- const crossKey = nodeKey("crossing", crossingName, sourceCtx.name);
63826
- if (!evtNode.triggers.includes(crossKey)) {
63827
- evtNode.triggers.push(crossKey);
63828
- }
63829
- }
64872
+ addCrossingNode(conn, flow, nodeMap, ir);
63830
64873
  }
63831
64874
  }
63832
64875
  }
64876
+ function addCrossingNode(conn, flow, nodeMap, ir) {
64877
+ const eventName = resolveEventName(conn.eventId, ir) ?? conn.schemaContract.eventType;
64878
+ if (!eventName)
64879
+ return;
64880
+ const sourceCtx = findSourceContextForEvent(conn.eventId, ir);
64881
+ const sourceCtxName = sourceCtx?.name ?? findProducingEntryContext(eventName, conn.sourceMomentId, flow, ir);
64882
+ if (!sourceCtxName)
64883
+ return;
64884
+ const targetCtxName = contextDisplayName2(conn.targetContextId, ir);
64885
+ const crossingName = `${eventName}->${targetCtxName}`;
64886
+ const crossingNode = getOrCreate(nodeMap, "crossing", crossingName, sourceCtxName);
64887
+ const evtKey = nodeKey("event", eventName, sourceCtxName);
64888
+ if (!crossingNode.dependsOn.includes(evtKey)) {
64889
+ crossingNode.dependsOn.push(evtKey);
64890
+ }
64891
+ const evtNode = nodeMap.get(evtKey);
64892
+ if (evtNode) {
64893
+ const crossKey = nodeKey("crossing", crossingName, sourceCtxName);
64894
+ if (!evtNode.triggers.includes(crossKey)) {
64895
+ evtNode.triggers.push(crossKey);
64896
+ }
64897
+ }
64898
+ }
64899
+ function findProducingEntryContext(eventName, sourceMomentId, flow, ir) {
64900
+ const moment = flow.moments.find((m) => m.id === sourceMomentId);
64901
+ if (!moment)
64902
+ return void 0;
64903
+ const entries = [
64904
+ ...moment.contextEntries,
64905
+ ...(moment.branches ?? []).flatMap((b) => b.entries)
64906
+ ];
64907
+ const producing = entries.find((e) => e.nodeName === eventName);
64908
+ return producing ? contextDisplayName2(producing.contextId, ir) : void 0;
64909
+ }
63833
64910
  function findSourceContextForEvent(eventId, ir) {
63834
64911
  for (const ctx of ir.contexts) {
63835
64912
  if (ctx.events.some((e) => e.id === eventId))
@@ -63873,9 +64950,8 @@ function buildStateMachine(saga, contextName) {
63873
64950
  const initialState = sagaStates.length > 0 ? sagaStates[0] : "";
63874
64951
  const finalState = sagaStates.length > 0 ? sagaStates[sagaStates.length - 1] : "";
63875
64952
  const states = buildStates(sagaStates, initialState, finalState);
63876
- const transitions = buildTransitions(sagaStates);
64953
+ const transitions = buildTransitions(saga);
63877
64954
  const reachability = computeReachability(states, transitions, initialState);
63878
- const earlyExitStates = sagaStates.filter((s) => s !== initialState && s !== finalState);
63879
64955
  return {
63880
64956
  sagaName: saga.name,
63881
64957
  context: contextName,
@@ -63885,7 +64961,6 @@ function buildStateMachine(saga, contextName) {
63885
64961
  transitions,
63886
64962
  compensation: saga.compensation,
63887
64963
  timeout: saga.timeout,
63888
- earlyExitStates,
63889
64964
  reachability
63890
64965
  };
63891
64966
  }
@@ -63896,7 +64971,15 @@ function buildStates(stateNames, initialState, finalState) {
63896
64971
  isFinal: name === finalState
63897
64972
  }));
63898
64973
  }
63899
- function buildTransitions(stateNames) {
64974
+ function buildTransitions(saga) {
64975
+ if (saga.transitions) {
64976
+ return saga.transitions.map((t) => ({
64977
+ from: t.from,
64978
+ to: t.to,
64979
+ ...t.onEvent !== void 0 ? { onEvent: t.onEvent } : {}
64980
+ }));
64981
+ }
64982
+ const stateNames = saga.states;
63900
64983
  const transitions = [];
63901
64984
  for (let i = 0; i < stateNames.length - 1; i++) {
63902
64985
  transitions.push({
@@ -63977,15 +65060,14 @@ function buildFrames(flow, ctxMap, laneIndex) {
63977
65060
  }
63978
65061
  function buildFrameNodes(moment, ctxMap, laneIndex) {
63979
65062
  const nodes = [];
63980
- let branchIdx = 0;
63981
- for (const entry of moment.contextEntries) {
63982
- nodes.push(entryToNode2(entry, moment, ctxMap, laneIndex, "main"));
63983
- }
63984
- for (const branch of moment.branches ?? []) {
63985
- for (const entry of branch.entries) {
63986
- nodes.push(entryToNode2(entry, moment, ctxMap, laneIndex, `br${branchIdx}`));
65063
+ for (const child of orderedMomentChildren(moment, "entries-first")) {
65064
+ if (child.kind === "entry") {
65065
+ nodes.push(entryToNode2(child.entry, moment, ctxMap, laneIndex, "main"));
65066
+ } else {
65067
+ for (const entry of child.branch.entries) {
65068
+ nodes.push(entryToNode2(entry, moment, ctxMap, laneIndex, `br${child.branchIndex}`));
65069
+ }
63987
65070
  }
63988
- branchIdx++;
63989
65071
  }
63990
65072
  return nodes;
63991
65073
  }
@@ -64031,15 +65113,43 @@ function findAggregate(nodeName, ctx) {
64031
65113
  }
64032
65114
  function buildConnections(flow) {
64033
65115
  return flow.connections.map((conn) => {
65116
+ const eventType = conn.eventId.replace(/^evt-/, "");
65117
+ const endpoints = {
65118
+ eventType,
65119
+ sourceNodeName: conn.sourceNodeName,
65120
+ targetNodeName: conn.targetNodeName
65121
+ };
65122
+ if (conn.connectionType === "returns-to") {
65123
+ if (conn.targetMomentId) {
65124
+ return {
65125
+ from: conn.sourceMomentId,
65126
+ to: conn.targetMomentId,
65127
+ style: "return",
65128
+ targetMomentLabel: conn.targetMomentLabel,
65129
+ ...endpoints
65130
+ };
65131
+ }
65132
+ return {
65133
+ from: conn.sourceMomentId,
65134
+ to: conn.targetContextId,
65135
+ label: `unresolved returns-to target: ${conn.targetMomentLabel ?? "(unknown)"}`,
65136
+ style: "return",
65137
+ targetMomentLabel: conn.targetMomentLabel,
65138
+ ...endpoints
65139
+ };
65140
+ }
64034
65141
  const isCrossing2 = conn.connectionType === "crosses-to";
64035
65142
  return {
64036
65143
  from: conn.sourceMomentId,
65144
+ // For 'triggers' the core now resolves targetContextId to the context
65145
+ // where the TARGET node is placed (not the source's own lane).
64037
65146
  to: conn.targetContextId,
64038
- label: isCrossing2 ? conn.eventId.replace(/^evt-/, "") : void 0,
65147
+ label: isCrossing2 ? eventType : void 0,
64039
65148
  style: mapConnectionStyle(conn),
64040
65149
  crossBoundary: isCrossing2 || void 0,
64041
65150
  relationship: isCrossing2 ? conn.schemaContract.relationshipType : void 0,
64042
- schemaContract: isCrossing2 ? mapSchemaContract(conn) : void 0
65151
+ schemaContract: isCrossing2 ? mapSchemaContract(conn) : void 0,
65152
+ ...endpoints
64043
65153
  };
64044
65154
  });
64045
65155
  }
@@ -64069,7 +65179,6 @@ function mapSchemaContract(conn) {
64069
65179
  function buildEventRouting(flow, ctxMap, laneIndex) {
64070
65180
  const routing = {};
64071
65181
  for (const moment of flow.moments) {
64072
- let branchIdx = 0;
64073
65182
  const addEntry = (entry, scope) => {
64074
65183
  const ctx = ctxMap.get(entry.contextId);
64075
65184
  const kind = resolveNodeKind2(entry, ctx);
@@ -64088,14 +65197,14 @@ function buildEventRouting(flow, ctxMap, laneIndex) {
64088
65197
  routing[key] = existing;
64089
65198
  }
64090
65199
  };
64091
- for (const entry of moment.contextEntries) {
64092
- addEntry(entry, "main");
64093
- }
64094
- for (const branch of moment.branches ?? []) {
64095
- for (const entry of branch.entries) {
64096
- addEntry(entry, `br${branchIdx}`);
65200
+ for (const child of orderedMomentChildren(moment, "entries-first")) {
65201
+ if (child.kind === "entry") {
65202
+ addEntry(child.entry, "main");
65203
+ } else {
65204
+ for (const entry of child.branch.entries) {
65205
+ addEntry(entry, `br${child.branchIndex}`);
65206
+ }
64097
65207
  }
64098
- branchIdx++;
64099
65208
  }
64100
65209
  }
64101
65210
  const result = {};
@@ -64111,8 +65220,7 @@ function buildBranchPredicates(flow) {
64111
65220
  continue;
64112
65221
  const routes = {};
64113
65222
  for (const branch of moment.branches) {
64114
- const hasTerminal = branch.entries.some((e) => e.terminal);
64115
- routes[branch.condition] = hasTerminal ? "terminal" : "happy";
65223
+ routes[branch.condition] = branch.condition;
64116
65224
  }
64117
65225
  predicates[moment.id] = {
64118
65226
  field: "outcome",
@@ -64124,7 +65232,8 @@ function buildBranchPredicates(flow) {
64124
65232
  }
64125
65233
  function buildContextMap2(flow, ctxMap) {
64126
65234
  const contexts = buildBoundedContexts(flow, ctxMap);
64127
- const relationships = buildContextRelationships(flow);
65235
+ const known = new Set(contexts.map((c) => c.contextId));
65236
+ const relationships = buildContextRelationships(flow).filter((r) => known.has(r.sourceContextId) && known.has(r.targetContextId));
64128
65237
  return { contexts, relationships };
64129
65238
  }
64130
65239
  function buildBoundedContexts(flow, ctxMap) {
@@ -64176,8 +65285,7 @@ function collectContextIds2(flow) {
64176
65285
  ids.add(entry.contextId);
64177
65286
  for (const branch of moment.branches ?? []) {
64178
65287
  for (const entry of branch.entries) {
64179
- if (!entry.terminal)
64180
- ids.add(entry.contextId);
65288
+ ids.add(entry.contextId);
64181
65289
  }
64182
65290
  }
64183
65291
  }
@@ -64213,6 +65321,7 @@ function renderFeatureHeader(lines, flow, ir) {
64213
65321
  }
64214
65322
  function renderMomentsByContext(lines, flow, ir, ctxMap) {
64215
65323
  const contextGroups = groupMomentsByContext(flow);
65324
+ const ownedSagaIds = collectOwnedSagaIds(flow, ir);
64216
65325
  for (const [ctxId, moments] of contextGroups) {
64217
65326
  const ctx = ctxMap.get(ctxId);
64218
65327
  const ctxName = ctx?.name ?? ctxId.replace(/^ctx-/, "");
@@ -64230,51 +65339,83 @@ function renderMomentsByContext(lines, flow, ir, ctxMap) {
64230
65339
  renderScenario(lines, moment, moment.contextEntries, flow, ctxMap, ir, void 0, sharedPreconditions);
64231
65340
  }
64232
65341
  }
64233
- renderSagaScenarios(lines, ctxId, ir);
65342
+ renderSagaScenarios(lines, ctxId, ir, ownedSagaIds);
64234
65343
  }
65344
+ renderOrphanOwnedSagas(lines, ir, contextGroups, ownedSagaIds);
64235
65345
  }
64236
65346
  function renderScenario(lines, moment, entries, flow, ctxMap, ir, variant, sharedPreconditions = /* @__PURE__ */ new Set()) {
64237
65347
  const label = variant ? `${moment.name} [${variant}]` : moment.name;
64238
- const tags = buildScenarioTags(entries, flow, ctxMap, ir, variant);
65348
+ const tags = buildScenarioTags(entries, moment, void 0, flow, ctxMap, ir);
64239
65349
  if (tags.length > 0)
64240
65350
  lines.push(` ${tags.join(" ")}`);
64241
65351
  lines.push(` Scenario: ${label}`);
64242
- for (const entry of entries) {
64243
- renderEntrySteps(lines, entry, flow, ctxMap, sharedPreconditions);
65352
+ const steps = [];
65353
+ const usedConnections = /* @__PURE__ */ new Set();
65354
+ for (const entry of orderedMainEntries(moment)) {
65355
+ renderEntrySteps(steps, entry, moment, void 0, flow, ctxMap, sharedPreconditions, usedConnections);
64244
65356
  }
65357
+ promoteLeadingAnd(steps);
65358
+ lines.push(...steps);
64245
65359
  lines.push("");
64246
65360
  }
64247
65361
  function renderBranchScenario(lines, moment, condition, branchEntries, flow, ctxMap, ir, sharedPreconditions = /* @__PURE__ */ new Set()) {
64248
65362
  const isTerminal = branchEntries.some((e) => e.terminal);
64249
65363
  if (isTerminal) {
64250
- renderTerminalBranchScenario(lines, moment, condition, flow, ctxMap, ir);
65364
+ renderTerminalBranchScenario(lines, moment, condition, branchEntries, flow, ctxMap, ir);
64251
65365
  return;
64252
65366
  }
64253
65367
  const allEntries = [...moment.contextEntries, ...branchEntries];
64254
- const tags = buildScenarioTags(allEntries, flow, ctxMap, ir, condition);
65368
+ const tags = buildScenarioTags(allEntries, moment, condition, flow, ctxMap, ir);
65369
+ const returnsTo = findReturnsTo(moment, condition, flow);
64255
65370
  if (!tags.includes("@failure-path"))
64256
- tags.push("@happy-path");
65371
+ tags.push(returnsTo ? "@retry-path" : "@alt-path");
64257
65372
  if (tags.length > 0)
64258
65373
  lines.push(` ${tags.join(" ")}`);
64259
65374
  lines.push(` Scenario: ${moment.name} [${condition}]`);
64260
- for (const entry of allEntries) {
64261
- renderEntrySteps(lines, entry, flow, ctxMap, sharedPreconditions);
64262
- }
65375
+ const steps = buildBranchScenarioSteps(moment, condition, branchEntries, flow, ctxMap, sharedPreconditions);
65376
+ if (returnsTo) {
65377
+ const targetLabel = resolveReturnsToLabel(returnsTo, flow);
65378
+ if (targetLabel)
65379
+ steps.push(` Then the flow returns to "${targetLabel}"`);
65380
+ }
65381
+ promoteLeadingAnd(steps);
65382
+ lines.push(...steps);
64263
65383
  lines.push("");
64264
65384
  }
64265
- function renderTerminalBranchScenario(lines, moment, condition, flow, ctxMap, ir) {
65385
+ function buildBranchScenarioSteps(moment, condition, branchEntries, flow, ctxMap, sharedPreconditions) {
65386
+ const steps = [];
65387
+ const usedConnections = /* @__PURE__ */ new Set();
65388
+ for (const child of orderedMomentChildren2(moment)) {
65389
+ if (child.kind === "entry") {
65390
+ renderEntrySteps(steps, child.entry, moment, void 0, flow, ctxMap, sharedPreconditions, usedConnections);
65391
+ } else if (child.branch.condition === condition && child.branch.entries === branchEntries) {
65392
+ for (const entry of child.branch.entries) {
65393
+ renderEntrySteps(steps, entry, moment, condition, flow, ctxMap, sharedPreconditions, usedConnections);
65394
+ }
65395
+ }
65396
+ }
65397
+ return steps;
65398
+ }
65399
+ function renderTerminalBranchScenario(lines, moment, condition, branchEntries, flow, ctxMap, ir) {
64266
65400
  const tags = ["@terminal", "@failure-path"];
64267
65401
  lines.push(` ${tags.join(" ")}`);
64268
65402
  lines.push(` Scenario: ${moment.name} [${condition}]`);
64269
- const failedPrecondition = findFailedPrecondition(moment, condition, ctxMap);
65403
+ const failedPrecondition = findFailedPrecondition(moment, ctxMap);
65404
+ lines.push(` Given the ${moment.name.toLowerCase()} is evaluated`);
64270
65405
  if (failedPrecondition) {
64271
- lines.push(` Given the ${moment.name.toLowerCase()} is evaluated`);
64272
65406
  lines.push(` When ${failedPrecondition.name} is not satisfied`);
64273
- lines.push(` Then the flow terminates because ${failedPrecondition.description}`);
64274
65407
  } else {
64275
- lines.push(` Given the ${moment.name.toLowerCase()} is evaluated`);
64276
65408
  lines.push(` When the outcome is ${condition}`);
64277
- lines.push(` Then the flow terminates`);
65409
+ }
65410
+ const usedConnections = /* @__PURE__ */ new Set();
65411
+ for (const entry of branchEntries) {
65412
+ renderEntrySteps(lines, entry, moment, condition, flow, ctxMap, /* @__PURE__ */ new Set(), usedConnections);
65413
+ }
65414
+ if (failedPrecondition) {
65415
+ lines.push(` Then the flow terminates because ${failedPrecondition.description}`);
65416
+ } else {
65417
+ const terminalNode = branchEntries.find((e) => e.terminal)?.nodeName;
65418
+ lines.push(` Then the flow terminates${terminalNode ? ` at ${terminalNode}` : ""}`);
64278
65419
  }
64279
65420
  const compensation = findCompensationForMoment(moment, ir);
64280
65421
  if (compensation) {
@@ -64282,10 +65423,10 @@ function renderTerminalBranchScenario(lines, moment, condition, flow, ctxMap, ir
64282
65423
  }
64283
65424
  lines.push("");
64284
65425
  }
64285
- function buildScenarioTags(entries, flow, ctxMap, ir, variant) {
65426
+ function buildScenarioTags(entries, moment, branchCondition, flow, ctxMap, ir) {
64286
65427
  const tags = [];
64287
65428
  collectAggregateTags(entries, ctxMap, tags);
64288
- if (entries.some((e) => findCrossing(e, flow)))
65429
+ if (entries.some((e) => findCrossing(e, moment, branchCondition, flow)))
64289
65430
  tags.push("@crossing");
64290
65431
  collectPolicyTags(entries, ctxMap, tags);
64291
65432
  collectSagaTags(entries, ir, tags);
@@ -64334,16 +65475,16 @@ function collectInvariantTags(entries, ctxMap, tags) {
64334
65475
  }
64335
65476
  }
64336
65477
  }
64337
- function renderEntrySteps(lines, entry, flow, ctxMap, sharedPreconditions = /* @__PURE__ */ new Set()) {
65478
+ function renderEntrySteps(lines, entry, moment, branchCondition, flow, ctxMap, sharedPreconditions = /* @__PURE__ */ new Set(), usedConnections = /* @__PURE__ */ new Set()) {
64338
65479
  const ctx = ctxMap.get(entry.contextId);
64339
65480
  const ctxName = entry.contextId.replace(/^ctx-/, "");
64340
- if (isCommand(entry.nodeName, ctx)) {
64341
- renderCommandStep(lines, entry, ctx, ctxName, flow, sharedPreconditions);
65481
+ if (entry.nodeKind === "command") {
65482
+ renderCommandStep(lines, entry, ctx, ctxName, moment, flow, sharedPreconditions);
64342
65483
  } else {
64343
- renderEventStep(lines, entry, ctx, ctxName, flow);
65484
+ renderEventStep(lines, entry, ctx, ctxName, moment, branchCondition, flow, usedConnections);
64344
65485
  }
64345
65486
  }
64346
- function renderCommandStep(lines, entry, ctx, ctxName, flow, sharedPreconditions = /* @__PURE__ */ new Set()) {
65487
+ function renderCommandStep(lines, entry, ctx, ctxName, moment, flow, sharedPreconditions = /* @__PURE__ */ new Set()) {
64347
65488
  const cmd = findCommand2(ctx, entry.nodeName);
64348
65489
  if (cmd) {
64349
65490
  for (const pre of cmd.preconditions) {
@@ -64352,7 +65493,7 @@ function renderCommandStep(lines, entry, ctx, ctxName, flow, sharedPreconditions
64352
65493
  }
64353
65494
  }
64354
65495
  }
64355
- const trigger = findTriggeredBy(entry, flow);
65496
+ const trigger = findTriggeredBy(entry, moment, flow);
64356
65497
  if (trigger) {
64357
65498
  lines.push(` And ${trigger} has occurred`);
64358
65499
  }
@@ -64380,9 +65521,10 @@ function renderInternalEventStep(lines, entry, evt, ctxName) {
64380
65521
  lines.push(` carrying ${evt.fields.map((f) => f.name).join(", ")}`);
64381
65522
  }
64382
65523
  }
64383
- function renderEventStep(lines, entry, ctx, ctxName, flow) {
64384
- const crossing = findCrossing(entry, flow);
65524
+ function renderEventStep(lines, entry, ctx, ctxName, moment, branchCondition, flow, usedConnections = /* @__PURE__ */ new Set()) {
65525
+ const crossing = findCrossing(entry, moment, branchCondition, flow, usedConnections);
64385
65526
  if (crossing) {
65527
+ usedConnections.add(crossing.id);
64386
65528
  renderCrossingStep(lines, entry, crossing);
64387
65529
  } else {
64388
65530
  renderInternalEventStep(lines, entry, findEvent2(ctx, entry.nodeName), ctxName);
@@ -64435,15 +65577,51 @@ function renderBackgroundBlock(lines, sharedPreconditions) {
64435
65577
  }
64436
65578
  lines.push("");
64437
65579
  }
64438
- function renderSagaScenarios(lines, ctxId, ir) {
65580
+ function collectOwnedSagaIds(flow, ir) {
65581
+ const owned = /* @__PURE__ */ new Set();
65582
+ for (const ctx of ir.contexts) {
65583
+ for (const saga of ctx.sagas) {
65584
+ const owner = ir.flows.find((f) => flowContainsNode2(f, saga.trigger)) ?? ir.flows[0];
65585
+ if (owner && owner.id === flow.id)
65586
+ owned.add(saga.id);
65587
+ }
65588
+ }
65589
+ return owned;
65590
+ }
65591
+ function flowContainsNode2(flow, nodeName) {
65592
+ for (const moment of flow.moments) {
65593
+ if (momentContainsNode(moment, nodeName))
65594
+ return true;
65595
+ }
65596
+ return false;
65597
+ }
65598
+ function renderSagaScenarios(lines, ctxId, ir, ownedSagaIds) {
64439
65599
  const ctx = ir.contexts.find((c) => c.id === ctxId);
64440
65600
  if (!ctx)
64441
65601
  return;
64442
65602
  for (const saga of ctx.sagas) {
65603
+ if (!ownedSagaIds.has(saga.id))
65604
+ continue;
64443
65605
  renderSagaTransitionScenario(lines, saga);
64444
65606
  renderSagaCompensationScenario(lines, saga);
64445
65607
  }
64446
65608
  }
65609
+ function renderOrphanOwnedSagas(lines, ir, contextGroups, ownedSagaIds) {
65610
+ for (const ctx of ir.contexts) {
65611
+ if (contextGroups.has(ctx.id))
65612
+ continue;
65613
+ const owned = ctx.sagas.filter((s) => ownedSagaIds.has(s.id));
65614
+ if (owned.length === 0)
65615
+ continue;
65616
+ const classification = ctx.classification ? ` [${ctx.classification}]` : "";
65617
+ lines.push(` Rule: ${ctx.name}${classification}`);
65618
+ lines.push("");
65619
+ for (const saga of owned) {
65620
+ renderSagaTransitionScenario(lines, saga);
65621
+ renderSagaCompensationScenario(lines, saga);
65622
+ }
65623
+ }
65624
+ }
64447
65625
  function renderSagaTransitionScenario(lines, saga) {
64448
65626
  lines.push(` @saga:${saga.name}`);
64449
65627
  lines.push(` Scenario: ${saga.name} state transitions`);
@@ -64459,17 +65637,14 @@ function renderSagaCompensationScenario(lines, saga) {
64459
65637
  lines.push(` Then ${saga.compensation} is executed`);
64460
65638
  lines.push("");
64461
65639
  }
64462
- function findFailedPrecondition(moment, _condition, ctxMap) {
64463
- if (!moment.branches)
64464
- return void 0;
64465
- const nonTerminalBranches = moment.branches.filter((b) => !b.entries.some((e) => e.terminal));
64466
- for (const branch of nonTerminalBranches) {
64467
- for (const entry of branch.entries) {
64468
- const ctx = ctxMap.get(entry.contextId);
64469
- const cmd = findCommand2(ctx, entry.nodeName);
64470
- if (cmd && cmd.preconditions.length > 0) {
64471
- return cmd.preconditions[0];
64472
- }
65640
+ function findFailedPrecondition(moment, ctxMap) {
65641
+ for (const entry of moment.contextEntries) {
65642
+ if (entry.nodeKind !== "command")
65643
+ continue;
65644
+ const ctx = ctxMap.get(entry.contextId);
65645
+ const cmd = findCommand2(ctx, entry.nodeName);
65646
+ if (cmd && cmd.preconditions.length > 0) {
65647
+ return cmd.preconditions[0];
64473
65648
  }
64474
65649
  }
64475
65650
  return void 0;
@@ -64489,10 +65664,47 @@ function findCompensationForMoment(moment, ir) {
64489
65664
  }
64490
65665
  return void 0;
64491
65666
  }
64492
- function isCommand(name, ctx) {
64493
- if (!ctx)
65667
+ function orderedMomentChildren2(moment) {
65668
+ const entries = moment.contextEntries;
65669
+ const branches = moment.branches ?? [];
65670
+ const seq = moment.sequence;
65671
+ if (seq && isUsableSequence2(seq, entries.length, branches.length)) {
65672
+ return seq.map((item) => item.kind === "entry" ? { kind: "entry", entry: entries[item.index] } : { kind: "branch", branch: branches[item.index] });
65673
+ }
65674
+ return [
65675
+ ...entries.map((entry) => ({ kind: "entry", entry })),
65676
+ ...branches.map((branch) => ({ kind: "branch", branch }))
65677
+ ];
65678
+ }
65679
+ function isUsableSequence2(seq, entryCount, branchCount) {
65680
+ if (seq.length !== entryCount + branchCount)
64494
65681
  return false;
64495
- return ctx.commands.some((c) => c.name === name);
65682
+ const seenEntries = /* @__PURE__ */ new Set();
65683
+ const seenBranches = /* @__PURE__ */ new Set();
65684
+ for (const item of seq) {
65685
+ const max = item.kind === "entry" ? entryCount : branchCount;
65686
+ const seen = item.kind === "entry" ? seenEntries : seenBranches;
65687
+ if (item.index < 0 || item.index >= max || seen.has(item.index))
65688
+ return false;
65689
+ seen.add(item.index);
65690
+ }
65691
+ return true;
65692
+ }
65693
+ function orderedMainEntries(moment) {
65694
+ const out = [];
65695
+ for (const child of orderedMomentChildren2(moment)) {
65696
+ if (child.kind === "entry")
65697
+ out.push(child.entry);
65698
+ }
65699
+ return out;
65700
+ }
65701
+ function promoteLeadingAnd(steps) {
65702
+ if (steps.length === 0)
65703
+ return;
65704
+ const first2 = steps[0];
65705
+ if (first2.trimStart().startsWith("And ")) {
65706
+ steps[0] = first2.replace("And ", "Given ");
65707
+ }
64496
65708
  }
64497
65709
  function findCommand2(ctx, name) {
64498
65710
  if (!ctx)
@@ -64532,19 +65744,36 @@ function momentContainsNode(moment, nodeName) {
64532
65744
  }
64533
65745
  return false;
64534
65746
  }
64535
- function findTriggeredBy(entry, flow) {
65747
+ function findTriggeredBy(entry, moment, flow) {
64536
65748
  for (const conn of flow.connections) {
64537
65749
  if (conn.connectionType !== "triggered-by")
64538
65750
  continue;
64539
65751
  const triggerEventName = conn.eventId.replace(/^evt-/, "");
64540
- const moment = flow.moments.find((m) => m.id === conn.sourceMomentId);
64541
- if (moment && momentContainsNode(moment, entry.nodeName))
64542
- return triggerEventName;
65752
+ if (conn.targetNodeName) {
65753
+ if (conn.sourceMomentId === moment.id && conn.targetNodeName === entry.nodeName) {
65754
+ return triggerEventName;
65755
+ }
65756
+ } else {
65757
+ const owner = flow.moments.find((m) => m.id === conn.sourceMomentId);
65758
+ if (owner && momentContainsNode(owner, entry.nodeName))
65759
+ return triggerEventName;
65760
+ }
64543
65761
  }
64544
65762
  return void 0;
64545
65763
  }
64546
- function findCrossing(entry, flow) {
64547
- return flow.connections.find((c) => c.connectionType === "crosses-to" && c.eventId === `evt-${entry.nodeName}`);
65764
+ function findCrossing(entry, moment, branchCondition, flow, usedConnections = /* @__PURE__ */ new Set()) {
65765
+ return flow.connections.find((c) => c.connectionType === "crosses-to" && c.sourceMomentId === moment.id && (c.sourceNodeName ? c.sourceNodeName === entry.nodeName : c.eventId === `evt-${entry.nodeName}`) && (c.branchCondition === void 0 || c.branchCondition === branchCondition) && !usedConnections.has(c.id));
65766
+ }
65767
+ function findReturnsTo(moment, branchCondition, flow) {
65768
+ return flow.connections.find((c) => c.connectionType === "returns-to" && c.sourceMomentId === moment.id && (c.branchCondition === void 0 || c.branchCondition === branchCondition));
65769
+ }
65770
+ function resolveReturnsToLabel(conn, flow) {
65771
+ if (conn.targetMomentId) {
65772
+ const target = flow.moments.find((m) => m.id === conn.targetMomentId);
65773
+ if (target)
65774
+ return target.name;
65775
+ }
65776
+ return "targetMomentLabel" in conn ? conn.targetMomentLabel : void 0;
64548
65777
  }
64549
65778
  function collectFlowContextIds(flow) {
64550
65779
  const ids = /* @__PURE__ */ new Set();
@@ -64713,8 +65942,9 @@ var SpecificationDocumentGenerator = class {
64713
65942
  }
64714
65943
  renderMomentNarrative(lines, moment, step, flow, ir) {
64715
65944
  this.renderMomentHeader(lines, moment, step, flow, ir);
65945
+ const usedConnections = /* @__PURE__ */ new Set();
64716
65946
  for (const entry of moment.contextEntries) {
64717
- this.renderEntryNarrative(lines, entry, flow, ir);
65947
+ this.renderEntryNarrative(lines, entry, moment, void 0, flow, ir, " ", usedConnections);
64718
65948
  }
64719
65949
  this.renderMomentBranches(lines, moment, flow, ir);
64720
65950
  this.renderMomentPoliciesAndRules(lines, moment, ir);
@@ -64738,15 +65968,35 @@ var SpecificationDocumentGenerator = class {
64738
65968
  for (const branch of moment.branches) {
64739
65969
  const isTerminal = branch.entries.some((e) => e.terminal);
64740
65970
  if (isTerminal) {
64741
- lines.push(` - \u274C **If ${branch.condition}** \u2192 flow terminates`);
65971
+ this.renderTerminalBranch(lines, moment, branch, flow, ir);
64742
65972
  } else {
64743
- lines.push(` - \u2705 **If ${branch.condition}:**`);
64744
- for (const entry of branch.entries) {
64745
- this.renderEntryNarrative(lines, entry, flow, ir, " ");
64746
- }
65973
+ this.renderNonTerminalBranch(lines, moment, branch, flow, ir);
64747
65974
  }
64748
65975
  }
64749
65976
  }
65977
+ renderTerminalBranch(lines, moment, branch, flow, ir) {
65978
+ const usedConnections = /* @__PURE__ */ new Set();
65979
+ const terminalNode = branch.entries.find((e) => e.terminal)?.nodeName;
65980
+ lines.push(` - \u274C **If ${branch.condition}:** flow terminates${terminalNode ? ` at **${terminalNode}**` : ""}`);
65981
+ for (const entry of branch.entries) {
65982
+ if (entry.terminal)
65983
+ continue;
65984
+ this.renderEntryNarrative(lines, entry, moment, branch.condition, flow, ir, " ", usedConnections);
65985
+ }
65986
+ }
65987
+ renderNonTerminalBranch(lines, moment, branch, flow, ir) {
65988
+ const usedConnections = /* @__PURE__ */ new Set();
65989
+ lines.push(` - \u2705 **If ${branch.condition}:**`);
65990
+ for (const entry of branch.entries) {
65991
+ this.renderEntryNarrative(lines, entry, moment, branch.condition, flow, ir, " ", usedConnections);
65992
+ }
65993
+ const returnsTo = this.findReturnsTo(moment, branch.condition, flow);
65994
+ if (returnsTo) {
65995
+ const label = this.resolveReturnsToLabel(returnsTo, flow);
65996
+ if (label)
65997
+ lines.push(` - \u21A9 *returns to "${label}"*`);
65998
+ }
65999
+ }
64750
66000
  renderMomentPoliciesAndRules(lines, moment, ir) {
64751
66001
  for (const pol of this.findPoliciesTriggeredBy(moment, ir)) {
64752
66002
  lines.push(` - \u{1F517} *Policy: ${pol.name}* \u2014 ${pol.action}`);
@@ -64755,13 +66005,16 @@ var SpecificationDocumentGenerator = class {
64755
66005
  lines.push(` - \u{1F4CB} *Rule ${rule.id}:* ${rule.description}`);
64756
66006
  }
64757
66007
  }
64758
- renderEntryNarrative(lines, entry, flow, ir, indent = " ") {
66008
+ renderEntryNarrative(lines, entry, moment, branchCondition, flow, ir, indent = " ", usedConnections = /* @__PURE__ */ new Set()) {
64759
66009
  const ctx = ir.contexts.find((c) => c.id === entry.contextId);
64760
66010
  const ctxName = entry.contextId.replace(/^ctx-/, "");
64761
- const crossing = this.findCrossing(entry.nodeName, flow);
64762
- if (this.isCommand(entry.nodeName, ctx)) {
66011
+ if (entry.nodeKind === "command") {
64763
66012
  this.renderCommandNarrative(lines, entry, ctx, ctxName, indent);
64764
- } else if (crossing) {
66013
+ return;
66014
+ }
66015
+ const crossing = this.findCrossing(entry, moment, branchCondition, flow, usedConnections);
66016
+ if (crossing) {
66017
+ usedConnections.add(crossing.id);
64765
66018
  const target = this.resolveCtxName(crossing.targetContextId, ir);
64766
66019
  lines.push(`${indent}- **${entry.nodeName}** \u2192 crosses to **${target}**`);
64767
66020
  } else {
@@ -64954,7 +66207,7 @@ var SpecificationDocumentGenerator = class {
64954
66207
  if (conn.connectionType !== "crosses-to")
64955
66208
  continue;
64956
66209
  const moment = flow.moments.find((m) => m.id === conn.sourceMomentId);
64957
- const sourceCtxId = this.resolveSourceContextId(moment);
66210
+ const sourceCtxId = moment ? this.findEntryForConnection(conn, moment)?.contextId : void 0;
64958
66211
  if (!sourceCtxId)
64959
66212
  continue;
64960
66213
  const sourceNode = this.mermaidNodeId(this.resolveCtxName(sourceCtxId, ir));
@@ -65019,7 +66272,7 @@ var SpecificationDocumentGenerator = class {
65019
66272
  }
65020
66273
  renderMomentSequenceMessages(lines, moment, flow, ir) {
65021
66274
  const ctxName = this.resolveMomentCtxName(moment, ir);
65022
- this.renderSequenceEntries(lines, moment.contextEntries, ctxName, flow, ir);
66275
+ this.renderSequenceEntries(lines, moment.contextEntries, moment, void 0, flow, ir);
65023
66276
  if (moment.branches && moment.branches.length > 0) {
65024
66277
  this.renderBranchAltBlock(lines, moment, ctxName, flow, ir);
65025
66278
  }
@@ -65033,8 +66286,7 @@ var SpecificationDocumentGenerator = class {
65033
66286
  const branch = nonTerminal[i];
65034
66287
  const keyword = i === 0 ? "alt" : "else";
65035
66288
  lines.push(` ${keyword} ${branch.condition}`);
65036
- const branchCtx = this.resolveBranchCtx(branch, ctxName, ir);
65037
- this.renderSequenceEntries(lines, branch.entries, branchCtx, flow, ir);
66289
+ this.renderSequenceEntries(lines, branch.entries, moment, branch.condition, flow, ir);
65038
66290
  }
65039
66291
  if (terminal.length > 0) {
65040
66292
  const keyword = nonTerminal.length === 0 ? "alt" : "else";
@@ -65044,24 +66296,22 @@ var SpecificationDocumentGenerator = class {
65044
66296
  }
65045
66297
  lines.push(" end");
65046
66298
  }
65047
- resolveBranchCtx(branch, fallback, ir) {
65048
- const entry = branch.entries.find((e) => !e.terminal);
65049
- return entry ? this.resolveCtxName(entry.contextId, ir) : fallback;
65050
- }
65051
- renderSequenceEntries(lines, entries, ctxName, flow, ir) {
66299
+ renderSequenceEntries(lines, entries, moment, branchCondition, flow, ir) {
66300
+ const usedConnections = /* @__PURE__ */ new Set();
65052
66301
  for (const entry of entries) {
65053
66302
  if (entry.terminal)
65054
66303
  continue;
65055
- this.renderSingleSequenceEntry(lines, entry, ctxName, flow, ir);
66304
+ this.renderSingleSequenceEntry(lines, entry, moment, branchCondition, flow, ir, usedConnections);
65056
66305
  }
65057
66306
  }
65058
- renderSingleSequenceEntry(lines, entry, ctxName, flow, ir) {
66307
+ renderSingleSequenceEntry(lines, entry, moment, branchCondition, flow, ir, usedConnections) {
65059
66308
  const entryCtx = this.resolveCtxName(entry.contextId, ir);
65060
- const crossing = this.findCrossing(entry.nodeName, flow);
66309
+ const crossing = this.findCrossing(entry, moment, branchCondition, flow, usedConnections);
65061
66310
  if (crossing) {
66311
+ usedConnections.add(crossing.id);
65062
66312
  const target = this.resolveCtxName(crossing.targetContextId, ir);
65063
66313
  lines.push(` ${entryCtx}->>${target}: ${entry.nodeName} (crosses)`);
65064
- } else if (this.isCommand(entry.nodeName, ir.contexts.find((c) => c.id === entry.contextId))) {
66314
+ } else if (entry.nodeKind === "command") {
65065
66315
  const cmd = this.findCommand(ir.contexts.find((c) => c.id === entry.contextId), entry.nodeName);
65066
66316
  const emits = cmd?.emitsEvent ? ` \u2192 ${cmd.emitsEvent}` : "";
65067
66317
  lines.push(` ${entryCtx}->>${entryCtx}: ${entry.nodeName}${emits}`);
@@ -65076,12 +66326,6 @@ var SpecificationDocumentGenerator = class {
65076
66326
  mermaidNodeId(name) {
65077
66327
  return name.replace(/[^a-zA-Z0-9]/g, "");
65078
66328
  }
65079
- resolveSourceContextId(moment) {
65080
- if (!moment)
65081
- return void 0;
65082
- const entry = moment.contextEntries[0] ?? moment.branches?.[0]?.entries?.[0];
65083
- return entry?.contextId;
65084
- }
65085
66329
  // ===========================================================================
65086
66330
  // Saga Helpers
65087
66331
  // ===========================================================================
@@ -65137,11 +66381,6 @@ var SpecificationDocumentGenerator = class {
65137
66381
  resolveCtxName(id, ir) {
65138
66382
  return ir.contexts.find((c) => c.id === id)?.name ?? id.replace(/^ctx-/, "");
65139
66383
  }
65140
- isCommand(name, ctx) {
65141
- if (!ctx)
65142
- return false;
65143
- return ctx.commands.some((c) => c.name === name);
65144
- }
65145
66384
  findCommand(ctx, name) {
65146
66385
  if (!ctx)
65147
66386
  return void 0;
@@ -65152,17 +66391,49 @@ var SpecificationDocumentGenerator = class {
65152
66391
  }
65153
66392
  return void 0;
65154
66393
  }
65155
- findCrossing(nodeName, flow) {
65156
- return flow.connections.find((c) => c.connectionType === "crosses-to" && c.eventId === `evt-${nodeName}`);
66394
+ /**
66395
+ * Finds the crossing declared by this entry in this moment (and branch).
66396
+ * Scoped to (sourceMomentId, node) so an event crossing to several targets
66397
+ * from different moments resolves to the right connection and contract.
66398
+ */
66399
+ findCrossing(entry, moment, branchCondition, flow, usedConnections = /* @__PURE__ */ new Set()) {
66400
+ return flow.connections.find((c) => c.connectionType === "crosses-to" && c.sourceMomentId === moment.id && (c.sourceNodeName ? c.sourceNodeName === entry.nodeName : c.eventId === `evt-${entry.nodeName}`) && (c.branchCondition === void 0 || c.branchCondition === branchCondition) && !usedConnections.has(c.id));
66401
+ }
66402
+ findReturnsTo(moment, branchCondition, flow) {
66403
+ return flow.connections.find((c) => c.connectionType === "returns-to" && c.sourceMomentId === moment.id && (c.branchCondition === void 0 || c.branchCondition === branchCondition));
66404
+ }
66405
+ resolveReturnsToLabel(conn, flow) {
66406
+ if (conn.targetMomentId) {
66407
+ const target = flow.moments.find((m) => m.id === conn.targetMomentId);
66408
+ if (target)
66409
+ return target.name;
66410
+ }
66411
+ return "targetMomentLabel" in conn ? conn.targetMomentLabel : void 0;
65157
66412
  }
65158
66413
  findSourceCtx(conn, moment, ir) {
65159
66414
  if (!moment)
65160
66415
  return "\u2014";
65161
- const entry = moment.contextEntries[0] ?? moment.branches?.[0]?.entries?.[0];
66416
+ const entry = this.findEntryForConnection(conn, moment);
65162
66417
  return entry ? this.resolveCtxName(entry.contextId, ir) : "\u2014";
65163
66418
  }
66419
+ /**
66420
+ * Resolves the entry that declared a connection, preferring the connection's
66421
+ * own sourceNodeName over positional guessing.
66422
+ */
66423
+ findEntryForConnection(conn, moment) {
66424
+ const allEntries = [
66425
+ ...moment.contextEntries,
66426
+ ...(moment.branches ?? []).flatMap((b) => b.entries)
66427
+ ];
66428
+ if (conn.sourceNodeName) {
66429
+ const declared = allEntries.find((e) => e.nodeName === conn.sourceNodeName);
66430
+ if (declared)
66431
+ return declared;
66432
+ }
66433
+ return allEntries[0];
66434
+ }
65164
66435
  findPoliciesTriggeredBy(moment, ir) {
65165
- const eventNames = moment.contextEntries.filter((e) => !this.isCommand(e.nodeName, ir.contexts.find((c) => c.id === e.contextId))).map((e) => e.nodeName);
66436
+ const eventNames = moment.contextEntries.filter((e) => e.nodeKind !== "command").map((e) => e.nodeName);
65166
66437
  const policies = [];
65167
66438
  for (const ctx of ir.contexts) {
65168
66439
  for (const pol of ctx.policies) {
@@ -65200,8 +66471,9 @@ function generateAsyncApiSpec(ir) {
65200
66471
  const crossings = collectCrossings(ir);
65201
66472
  const channels = buildChannelYaml(crossings);
65202
66473
  const messages = buildMessageYaml(crossings);
65203
- const title = escapeYaml(ir.metadata.name || "Untitled");
65204
- const version = escapeYaml(ir.metadata.version || "1.0.0");
66474
+ const title = escapeYaml(ir.metadata.name || "Moment Specification");
66475
+ const rawVersion = ir.metadata.version;
66476
+ const version = escapeYaml(!rawVersion || rawVersion === "0.0.0" ? "1.0.0" : rawVersion);
65205
66477
  const description = escapeYaml(ir.metadata.description || "");
65206
66478
  const parts = [];
65207
66479
  parts.push(`asyncapi: '3.0.0'`);
@@ -65229,27 +66501,58 @@ function collectCrossings(ir) {
65229
66501
  for (const conn of flow.connections) {
65230
66502
  if (conn.connectionType !== "crosses-to")
65231
66503
  continue;
65232
- const event = findEventById(conn.eventId, ir);
65233
- if (!event)
66504
+ const info = crossingFromConnection(conn, flow, ir);
66505
+ if (!info)
65234
66506
  continue;
65235
- const producerCtx = findProducerContext(conn.eventId, ir);
65236
- const consumerCtx = ir.contexts.find((c) => c.id === conn.targetContextId);
65237
- if (!producerCtx || !consumerCtx)
65238
- continue;
65239
- const key = `${event.name}|${producerCtx.name}|${consumerCtx.name}`;
66507
+ const key = `${info.eventName}|${info.producerContext}|${info.consumerContext}`;
65240
66508
  if (seen.has(key))
65241
66509
  continue;
65242
66510
  seen.add(key);
65243
- crossings.push({
65244
- eventName: event.name,
65245
- eventFields: event.fields.map((f) => ({ name: f.name, type: f.type, deprecated: f.deprecated ? true : void 0 })),
65246
- producerContext: producerCtx.name,
65247
- consumerContext: consumerCtx.name
65248
- });
66511
+ crossings.push(info);
65249
66512
  }
65250
66513
  }
65251
66514
  return crossings;
65252
66515
  }
66516
+ function crossingFromConnection(conn, flow, ir) {
66517
+ const event = findEventById(conn.eventId, ir);
66518
+ const eventName = event?.name ?? conn.schemaContract.eventType;
66519
+ if (!eventName)
66520
+ return void 0;
66521
+ const producerCtx = findProducerContext(conn.eventId, ir);
66522
+ const producerName = producerCtx?.name ?? findProducingEntryContext2(eventName, conn.sourceMomentId, flow, ir);
66523
+ if (!producerName)
66524
+ return void 0;
66525
+ const consumerCtx = ir.contexts.find((c) => c.id === conn.targetContextId);
66526
+ const consumerName = consumerCtx?.name ?? conn.targetContextId.replace(/^ctx-/, "");
66527
+ const { eventFields, requiredFields } = crossingSchema(event, conn);
66528
+ return {
66529
+ eventName,
66530
+ eventFields,
66531
+ requiredFields,
66532
+ producerContext: producerName,
66533
+ consumerContext: consumerName
66534
+ };
66535
+ }
66536
+ function crossingSchema(event, conn) {
66537
+ const eventFields = event ? event.fields.map((f) => ({ name: f.name, type: f.type, deprecated: f.deprecated ? true : void 0 })) : conn.schemaContract.fields.map((f) => ({ name: f.name, type: f.type, deprecated: void 0 }));
66538
+ const contractRequired = conn.schemaContract.fields.filter((f) => f.required).map((f) => f.name);
66539
+ const requiredFields = contractRequired.length > 0 ? contractRequired : event?.fields.filter((f) => f.required).map((f) => f.name) ?? [];
66540
+ return { eventFields, requiredFields };
66541
+ }
66542
+ function findProducingEntryContext2(eventName, sourceMomentId, flow, ir) {
66543
+ const moment = flow.moments.find((m) => m.id === sourceMomentId);
66544
+ if (!moment)
66545
+ return void 0;
66546
+ const entries = [
66547
+ ...moment.contextEntries,
66548
+ ...(moment.branches ?? []).flatMap((b) => b.entries)
66549
+ ];
66550
+ const producing = entries.find((e) => e.nodeName === eventName);
66551
+ if (!producing)
66552
+ return void 0;
66553
+ const ctx = ir.contexts.find((c) => c.id === producing.contextId);
66554
+ return ctx?.name ?? producing.contextId.replace(/^ctx-/, "");
66555
+ }
65253
66556
  function findEventById(eventId, ir) {
65254
66557
  for (const ctx of ir.contexts) {
65255
66558
  for (const evt of ctx.events) {
@@ -65277,15 +66580,31 @@ function findProducerContext(eventId, ir) {
65277
66580
  return void 0;
65278
66581
  }
65279
66582
  function buildChannelYaml(crossings) {
65280
- const lines = [];
66583
+ const byEvent = /* @__PURE__ */ new Map();
65281
66584
  for (const crossing of crossings) {
65282
- const channelName = toKebabCase(crossing.eventName);
66585
+ let group = byEvent.get(crossing.eventName);
66586
+ if (!group) {
66587
+ group = { producers: [], consumers: [] };
66588
+ byEvent.set(crossing.eventName, group);
66589
+ }
66590
+ if (!group.producers.includes(crossing.producerContext)) {
66591
+ group.producers.push(crossing.producerContext);
66592
+ }
66593
+ if (!group.consumers.includes(crossing.consumerContext)) {
66594
+ group.consumers.push(crossing.consumerContext);
66595
+ }
66596
+ }
66597
+ const lines = [];
66598
+ for (const [eventName, group] of byEvent) {
66599
+ const channelName = toKebabCase(eventName);
66600
+ const producers = group.producers.map(escapeYaml).join(", ");
66601
+ const consumers = group.consumers.map(escapeYaml).join(", ");
65283
66602
  lines.push(` ${channelName}:`);
65284
66603
  lines.push(` address: '${channelName}'`);
65285
66604
  lines.push(` messages:`);
65286
- lines.push(` ${crossing.eventName}:`);
65287
- lines.push(` $ref: '#/components/messages/${crossing.eventName}'`);
65288
- lines.push(` description: 'Published by ${escapeYaml(crossing.producerContext)}, consumed by ${escapeYaml(crossing.consumerContext)}'`);
66605
+ lines.push(` ${eventName}:`);
66606
+ lines.push(` $ref: '#/components/messages/${eventName}'`);
66607
+ lines.push(` description: 'Published by ${producers}, consumed by ${consumers}'`);
65289
66608
  }
65290
66609
  return lines.join("\n");
65291
66610
  }
@@ -65308,6 +66627,12 @@ function buildMessageYaml(crossings) {
65308
66627
  lines.push(` deprecated: true`);
65309
66628
  }
65310
66629
  }
66630
+ if (crossing.requiredFields.length > 0) {
66631
+ lines.push(` required:`);
66632
+ for (const name of crossing.requiredFields) {
66633
+ lines.push(` - ${name}`);
66634
+ }
66635
+ }
65311
66636
  }
65312
66637
  return lines.join("\n");
65313
66638
  }
@@ -65399,7 +66724,7 @@ function isStepLine(line) {
65399
66724
  function parseStepLine(line, lineNumber) {
65400
66725
  const keyword = line.split(" ")[0] + " ";
65401
66726
  const name = line.slice(keyword.length);
65402
- return { keyword, name, line: lineNumber, result: { status: "passed", duration: 0 } };
66727
+ return { keyword, name, line: lineNumber, result: { status: "pending", duration: 0 } };
65403
66728
  }
65404
66729
  function buildScenario(parsed, flow, suite) {
65405
66730
  const status = resolveScenarioStatus(parsed.name, parsed.tags, flow, suite);
@@ -65420,23 +66745,22 @@ function buildScenario(parsed, flow, suite) {
65420
66745
  function resolveScenarioStatus(scenarioName, tags, flow, suite) {
65421
66746
  if (!suite)
65422
66747
  return "skipped";
66748
+ const bracketIdx = scenarioName.indexOf(" [");
66749
+ const momentName = bracketIdx !== -1 ? scenarioName.slice(0, bracketIdx) : scenarioName;
66750
+ const variant = bracketIdx !== -1 ? scenarioName.slice(bracketIdx + 2, scenarioName.length - 1) : void 0;
66751
+ const moment = flow.moments.find((m) => m.name === momentName);
66752
+ if (moment) {
66753
+ const matchingCase = suite.testCases.find((tc) => tc.momentId === moment.id && (variant === void 0 || tc.variant === variant));
66754
+ return matchingCase ? "pending" : "skipped";
66755
+ }
65423
66756
  const isSaga = tags.some((t) => t.name.startsWith("@saga:"));
65424
- const isCompensation = tags.some((t) => t.name === "@compensation");
65425
66757
  if (isSaga) {
65426
- const sagaPrefix = isCompensation ? "saga-" : "saga-";
65427
- const matchingCases = suite.testCases.filter((tc) => tc.momentId.startsWith(sagaPrefix));
66758
+ const matchingCases = suite.testCases.filter((tc) => tc.momentId.startsWith("saga-"));
65428
66759
  if (matchingCases.length === 0)
65429
66760
  return "skipped";
65430
- return "passed";
66761
+ return "pending";
65431
66762
  }
65432
- const bracketIdx = scenarioName.indexOf(" [");
65433
- const momentName = bracketIdx !== -1 ? scenarioName.slice(0, bracketIdx) : scenarioName;
65434
- const variant = bracketIdx !== -1 ? scenarioName.slice(bracketIdx + 2, scenarioName.length - 1) : void 0;
65435
- const moment = flow.moments.find((m) => m.name === momentName);
65436
- if (!moment)
65437
- return "skipped";
65438
- const matchingCase = suite.testCases.find((tc) => tc.momentId === moment.id && (variant === void 0 || tc.variant === variant));
65439
- return matchingCase ? "passed" : "skipped";
66763
+ return "skipped";
65440
66764
  }
65441
66765
  function extractFeatureTags(content) {
65442
66766
  const firstLine = content.split("\n")[0]?.trim() ?? "";
@@ -65748,27 +67072,35 @@ function sanitizeComment(s) {
65748
67072
  function generateSetupComment(step) {
65749
67073
  return ` // Setup: ${sanitizeComment(step.contextName)}.${sanitizeComment(step.aggregateName)} \u2014 ${sanitizeComment(step.precondition)}`;
65750
67074
  }
65751
- function generateAssertionIt(assertion) {
67075
+ function assertionTestName(assertion, testCase) {
67076
+ if (assertion.assertionType === "saga" || assertion.assertionType === "policy-chain") {
67077
+ return `should verify ${escapeStringLiteral(testCase.momentName)}`;
67078
+ }
65752
67079
  const eventType = assertion.schemaContract.eventType;
65753
67080
  const source = assertion.sourceContext;
65754
67081
  const target = assertion.targetContext;
67082
+ return `should verify ${escapeStringLiteral(eventType)} crosses from ${escapeStringLiteral(source)} to ${escapeStringLiteral(target)}`;
67083
+ }
67084
+ function generateAssertionIt(assertion, testCase) {
65755
67085
  const lines = [];
65756
- lines.push(` it('should verify ${escapeStringLiteral(eventType)} crosses from ${escapeStringLiteral(source)} to ${escapeStringLiteral(target)}', () => {`);
65757
- lines.push(` // Crossing: ${assertion.crossingId}`);
65758
- lines.push(` // Assertion type: ${assertion.assertionType}`);
67086
+ lines.push(` // Crossing: ${assertion.crossingId}`);
67087
+ lines.push(` // Assertion type: ${assertion.assertionType}`);
65759
67088
  if (assertion.schemaContract.expectedFields.length > 0) {
65760
- lines.push(` // Expected fields:`);
67089
+ lines.push(` // Expected fields:`);
65761
67090
  for (const field of assertion.schemaContract.expectedFields) {
65762
- lines.push(` // ${field.fieldName}: ${field.expectedType} (${field.required ? "required" : "optional"})`);
67091
+ lines.push(` // ${field.fieldName}: ${field.expectedType} (${field.required ? "required" : "optional"})`);
65763
67092
  }
65764
67093
  }
65765
- lines.push(` // TODO: implement assertion`);
65766
- lines.push(` });`);
67094
+ lines.push(` it.todo('${assertionTestName(assertion, testCase)}');`);
65767
67095
  return lines.join("\n");
65768
67096
  }
67097
+ function describeName(testCase) {
67098
+ const base = escapeStringLiteral(testCase.momentName);
67099
+ return testCase.variant ? `${base} [${escapeStringLiteral(testCase.variant)}]` : base;
67100
+ }
65769
67101
  function generateTestCase(testCase) {
65770
67102
  const lines = [];
65771
- lines.push(` describe('${escapeStringLiteral(testCase.momentName)}', () => {`);
67103
+ lines.push(` describe('${describeName(testCase)}', () => {`);
65772
67104
  if (testCase.setupSteps.length > 0) {
65773
67105
  lines.push(` beforeEach(() => {`);
65774
67106
  for (const step of testCase.setupSteps) {
@@ -65778,7 +67110,7 @@ function generateTestCase(testCase) {
65778
67110
  lines.push("");
65779
67111
  }
65780
67112
  for (const assertion of testCase.assertions) {
65781
- lines.push(generateAssertionIt(assertion));
67113
+ lines.push(generateAssertionIt(assertion, testCase));
65782
67114
  lines.push("");
65783
67115
  }
65784
67116
  if (lines[lines.length - 1] === "") {
@@ -65787,12 +67119,12 @@ function generateTestCase(testCase) {
65787
67119
  lines.push(` });`);
65788
67120
  return lines.join("\n");
65789
67121
  }
65790
- function hasAnySetupSteps(suite) {
65791
- return suite.testCases.some((tc) => tc.setupSteps.length > 0);
67122
+ function renderableTestCases(suite) {
67123
+ return suite.testCases.filter((tc) => tc.assertions.length > 0);
65792
67124
  }
65793
- function generateFlowSpecFile(suite) {
67125
+ function generateFlowSpecFile(suite, testCases) {
65794
67126
  const lines = [];
65795
- const needsBeforeEach = hasAnySetupSteps(suite);
67127
+ const needsBeforeEach = testCases.some((tc) => tc.setupSteps.length > 0);
65796
67128
  if (needsBeforeEach) {
65797
67129
  lines.push("import { describe, it, beforeEach, expect } from 'vitest';");
65798
67130
  } else {
@@ -65800,9 +67132,9 @@ function generateFlowSpecFile(suite) {
65800
67132
  }
65801
67133
  lines.push("");
65802
67134
  lines.push(`describe('${escapeStringLiteral(suite.flowName)}', () => {`);
65803
- for (let i = 0; i < suite.testCases.length; i++) {
65804
- lines.push(generateTestCase(suite.testCases[i]));
65805
- if (i < suite.testCases.length - 1) {
67135
+ for (let i = 0; i < testCases.length; i++) {
67136
+ lines.push(generateTestCase(testCases[i]));
67137
+ if (i < testCases.length - 1) {
65806
67138
  lines.push("");
65807
67139
  }
65808
67140
  }
@@ -65816,15 +67148,12 @@ function generateAggregateSpecFile(aggregate) {
65816
67148
  lines.push("");
65817
67149
  lines.push(`describe('${escapeStringLiteral(aggregate.name)}', () => {`);
65818
67150
  for (const command2 of aggregate.commands) {
65819
- lines.push(` it('should handle ${escapeStringLiteral(command2.name)}', () => {`);
65820
- lines.push(` // TODO: implement`);
65821
- lines.push(` });`);
67151
+ lines.push(` it.todo('should handle ${escapeStringLiteral(command2.name)}');`);
65822
67152
  lines.push("");
65823
67153
  }
65824
67154
  for (const invariant of aggregate.invariants) {
65825
- lines.push(` it('should enforce invariant: ${escapeStringLiteral(invariant.description)}', () => {`);
65826
- lines.push(` // TODO: implement invariant violation test`);
65827
- lines.push(` });`);
67155
+ lines.push(` // TODO: implement invariant violation test`);
67156
+ lines.push(` it.todo('should enforce invariant: ${escapeStringLiteral(invariant.description)}');`);
65828
67157
  lines.push("");
65829
67158
  }
65830
67159
  if (lines[lines.length - 1] === "") {
@@ -65852,8 +67181,11 @@ var TestScaffoldEmitter = class {
65852
67181
  const files = /* @__PURE__ */ new Map();
65853
67182
  const specFilesWritten = [];
65854
67183
  for (const suite of topology.suites) {
67184
+ const testCases = renderableTestCases(suite);
67185
+ if (testCases.length === 0)
67186
+ continue;
65855
67187
  const fileName = `__tests__/flows/${safePathSegment2(suite.flowName)}.spec.ts`;
65856
- const content = generateFlowSpecFile(suite);
67188
+ const content = generateFlowSpecFile(suite, testCases);
65857
67189
  files.set(fileName, content);
65858
67190
  specFilesWritten.push(fileName);
65859
67191
  }
@@ -65861,6 +67193,8 @@ var TestScaffoldEmitter = class {
65861
67193
  for (const context of ir.contexts) {
65862
67194
  for (const aggregate of context.aggregates) {
65863
67195
  allAggregates.push(aggregate);
67196
+ if (aggregate.commands.length === 0 && aggregate.invariants.length === 0)
67197
+ continue;
65864
67198
  const contextDir = safePathSegment2(context.name);
65865
67199
  const fileName = `__tests__/${contextDir}/${safePathSegment2(aggregate.name)}.spec.ts`;
65866
67200
  const content = generateAggregateSpecFile(aggregate);
@@ -65877,6 +67211,31 @@ var TestScaffoldEmitter = class {
65877
67211
  }
65878
67212
  };
65879
67213
 
67214
+ // src/lib/flags.ts
67215
+ function collectUnknownFlags(tokens, knownOptions) {
67216
+ const known = new Set(knownOptions);
67217
+ const unknown = [];
67218
+ for (const token of tokens) {
67219
+ if (token.kind !== "option" || !token.name) continue;
67220
+ if (known.has(token.name)) continue;
67221
+ const raw = token.rawName ?? `--${token.name}`;
67222
+ if (!unknown.includes(raw)) unknown.push(raw);
67223
+ }
67224
+ return unknown;
67225
+ }
67226
+ function resolveStringFlag(values2, ...names) {
67227
+ for (const name of names) {
67228
+ const value = values2[name];
67229
+ if (typeof value === "string") return value;
67230
+ }
67231
+ return void 0;
67232
+ }
67233
+ function warnUnknownFlags(tokens, knownOptions, warn = (m) => console.warn(m)) {
67234
+ for (const flag of collectUnknownFlags(tokens, knownOptions)) {
67235
+ warn(`Warning: unrecognized flag ${flag} (ignored)`);
67236
+ }
67237
+ }
67238
+
65880
67239
  // src/commands/generate.ts
65881
67240
  var EMPTY2 = [];
65882
67241
  function fail2(message) {
@@ -65891,16 +67250,21 @@ function readFile2(path) {
65891
67250
  }
65892
67251
  }
65893
67252
  async function runGenerate(argv) {
65894
- const { values: values2, positionals } = parseArgs2({
67253
+ const options = {
67254
+ out: { type: "string", short: "o" },
67255
+ // Alias for --out, symmetric with `moment simulate --out-dir`.
67256
+ "out-dir": { type: "string" }
67257
+ };
67258
+ const { values: values2, positionals, tokens } = parseArgs2({
65895
67259
  args: argv,
65896
- options: {
65897
- out: { type: "string", short: "o" }
65898
- },
67260
+ options,
65899
67261
  allowPositionals: true,
65900
- strict: false
67262
+ strict: false,
67263
+ tokens: true
65901
67264
  });
67265
+ warnUnknownFlags(tokens, Object.keys(options));
65902
67266
  const filePath = positionals[0];
65903
- if (!filePath) return fail2("Usage: moment generate <file.moment> [--out <dir>]");
67267
+ if (!filePath) return fail2("Usage: moment generate <file.moment> [--out <dir> | --out-dir <dir>]");
65904
67268
  const resolvedPath = resolve8(filePath);
65905
67269
  if (!existsSync5(resolvedPath)) return fail2(`Error: File not found: ${resolvedPath}`);
65906
67270
  const content = readFile2(resolvedPath);
@@ -65926,10 +67290,7 @@ async function runGenerate(argv) {
65926
67290
  const docs = docGen.generate(ir);
65927
67291
  const tsOutput = tsEmitter.emit(ir, { scope: { level: "system" } });
65928
67292
  const scaffoldOutput = scaffoldEmitter.emit(ir, topology);
65929
- const outDir = resolveOutputBaseDir(
65930
- resolvedPath,
65931
- typeof values2.out === "string" ? values2.out : void 0
65932
- );
67293
+ const outDir = resolveOutputBaseDir(resolvedPath, resolveStringFlag(values2, "out-dir", "out"));
65933
67294
  const allFiles = /* @__PURE__ */ new Map();
65934
67295
  for (const [path, content2] of tsOutput.files) allFiles.set(path, content2);
65935
67296
  for (const [path, content2] of scaffoldOutput.files) allFiles.set(path, content2);
@@ -69890,13 +71251,14 @@ function writeOutputFiles3(groups, ir, outDir) {
69890
71251
  manifestScenarios.push({
69891
71252
  scenarioId: scenario.scenarioId,
69892
71253
  scenarioLabel: scenario.scenarioLabel,
71254
+ kind: scenario.kind,
69893
71255
  description: scenario.description,
69894
71256
  file: fileName,
69895
71257
  eventCount: scenario.events.length,
69896
71258
  pathLength: scenario.expectedPath.length,
69897
71259
  branchCount: scenario.activeBranches.length,
69898
- isHappyPath: scenario.scenarioLabel.startsWith("Happy Path:"),
69899
- isNegative: scenario.scenarioLabel.startsWith("Failure:")
71260
+ isHappyPath: scenario.kind === "happy",
71261
+ isNegative: scenario.kind === "negative"
69900
71262
  });
69901
71263
  }
69902
71264
  manifestFlows.push({
@@ -69926,6 +71288,9 @@ function writeArtifacts(ir, outDir) {
69926
71288
  const sagas = generateSagaStateMachines(ir);
69927
71289
  files.sagaStateMachines = "saga-state-machines.json";
69928
71290
  writeSafe(outDir, files.sagaStateMachines, JSON.stringify(sagas, null, 2) + "\n");
71291
+ const policies = generatePolicyDefinitions(ir);
71292
+ files.policyDefinitions = "policy-definitions.json";
71293
+ writeSafe(outDir, files.policyDefinitions, JSON.stringify(policies, null, 2) + "\n");
69929
71294
  const asyncapi = generateAsyncApiSpec(ir);
69930
71295
  files.asyncApi = "asyncapi.yaml";
69931
71296
  writeSafe(outDir, files.asyncApi, asyncapi);
@@ -69937,7 +71302,13 @@ function kebab3(s) {
69937
71302
  function runOutDirWrite(flows, ir, outDir, includeAll) {
69938
71303
  const groups = flows.map((flow) => ({
69939
71304
  flow,
69940
- scenarios: includeAll ? [...generateAllScenarios(ir, flow), ...deriveNegativeScenarios(ir, flow)] : [generateSimulationScenario(ir, flow)]
71305
+ scenarios: includeAll ? [
71306
+ ...generateAllScenarios(ir, flow),
71307
+ ...deriveNegativeScenarios(ir, flow),
71308
+ // Saga-timeout scenarios: neither happy nor negative — the manifest
71309
+ // carries kind:'timeout' with both flags false.
71310
+ ...deriveTimeoutScenarios(ir, flow)
71311
+ ] : [generateSimulationScenario(ir, flow)]
69941
71312
  }));
69942
71313
  try {
69943
71314
  return writeOutputFiles3(groups, ir, outDir);
@@ -69947,17 +71318,22 @@ function runOutDirWrite(flows, ir, outDir, includeAll) {
69947
71318
  }
69948
71319
  }
69949
71320
  async function runSimulate(argv) {
69950
- const { values: values2, positionals } = parseArgs18({
71321
+ const options = {
71322
+ json: { type: "boolean" },
71323
+ flow: { type: "string" },
71324
+ all: { type: "boolean" },
71325
+ "out-dir": { type: "string" },
71326
+ // Alias for --out-dir, symmetric with `moment generate --out`.
71327
+ out: { type: "string" }
71328
+ };
71329
+ const { values: values2, positionals, tokens } = parseArgs18({
69951
71330
  args: argv,
69952
- options: {
69953
- json: { type: "boolean" },
69954
- flow: { type: "string" },
69955
- all: { type: "boolean" },
69956
- "out-dir": { type: "string" }
69957
- },
71331
+ options,
69958
71332
  allowPositionals: true,
69959
- strict: false
71333
+ strict: false,
71334
+ tokens: true
69960
71335
  });
71336
+ warnUnknownFlags(tokens, Object.keys(options));
69961
71337
  const filePath = positionals[0];
69962
71338
  if (!filePath)
69963
71339
  return fail16(
@@ -69984,13 +71360,14 @@ async function runSimulate(argv) {
69984
71360
  if (targetFlows.length === 0) {
69985
71361
  return fail16(`Flow '${values2.flow}' not found.`);
69986
71362
  }
69987
- const outDir = values2["out-dir"];
71363
+ const outDir = resolveStringFlag(values2, "out-dir", "out");
69988
71364
  if (outDir) {
69989
71365
  return runOutDirWrite(targetFlows, ir, outDir, values2.all === true);
69990
71366
  }
69991
71367
  const scenarios = values2.all ? targetFlows.flatMap((flow) => [
69992
71368
  ...generateAllScenarios(ir, flow),
69993
- ...deriveNegativeScenarios(ir, flow)
71369
+ ...deriveNegativeScenarios(ir, flow),
71370
+ ...deriveTimeoutScenarios(ir, flow)
69994
71371
  ]) : targetFlows.map((flow) => generateSimulationScenario(ir, flow));
69995
71372
  return formatScenarios(scenarios, values2.json === true);
69996
71373
  }
@@ -70000,8 +71377,8 @@ function getClientId() {
70000
71377
  if (process.env.MOMENT_GITHUB_CLIENT_ID) {
70001
71378
  return process.env.MOMENT_GITHUB_CLIENT_ID;
70002
71379
  }
70003
- if ("Ov23li5nhWuDstHvLltG") {
70004
- return "Ov23li5nhWuDstHvLltG";
71380
+ if ("") {
71381
+ return "";
70005
71382
  }
70006
71383
  throw new Error(
70007
71384
  "GitHub App client ID not configured. Set MOMENT_GITHUB_CLIENT_ID or build with GH_APP_CLIENT_ID."