libpetri 1.8.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +47 -0
  2. package/dist/{chunk-B2D5DMTO.js → chunk-B2CV5M3L.js} +228 -9
  3. package/dist/chunk-B2CV5M3L.js.map +1 -0
  4. package/dist/chunk-H62Z76FY.js +1346 -0
  5. package/dist/chunk-H62Z76FY.js.map +1 -0
  6. package/dist/chunk-RNWTYK5B.js +419 -0
  7. package/dist/chunk-RNWTYK5B.js.map +1 -0
  8. package/dist/chunk-SXK2Z45Z.js +50 -0
  9. package/dist/chunk-SXK2Z45Z.js.map +1 -0
  10. package/dist/debug/index.d.ts +50 -3
  11. package/dist/debug/index.js +64 -6
  12. package/dist/debug/index.js.map +1 -1
  13. package/dist/doclet/index.d.ts +152 -31
  14. package/dist/doclet/index.js +458 -57
  15. package/dist/doclet/index.js.map +1 -1
  16. package/dist/doclet/resources/petrinet-diagrams.css +640 -7
  17. package/dist/doclet/resources/petrinet-diagrams.js +7238 -114
  18. package/dist/dot-exporter-WJMCJEFK.js +8 -0
  19. package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
  20. package/dist/export/index.d.ts +18 -4
  21. package/dist/export/index.js +1 -1
  22. package/dist/index.d.ts +41 -5
  23. package/dist/index.js +1221 -35
  24. package/dist/index.js.map +1 -1
  25. package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
  26. package/dist/render-QK57X4TP.js +73 -0
  27. package/dist/render-QK57X4TP.js.map +1 -0
  28. package/dist/verification/index.d.ts +3 -144
  29. package/dist/verification/index.js +30 -1214
  30. package/dist/verification/index.js.map +1 -1
  31. package/dist/viewer/index.d.ts +227 -0
  32. package/dist/viewer/index.js +877 -0
  33. package/dist/viewer/index.js.map +1 -0
  34. package/dist/viewer/layout/index.d.ts +200 -0
  35. package/dist/viewer/layout/index.js +15 -0
  36. package/dist/viewer/layout/index.js.map +1 -0
  37. package/dist/viewer/viewer-static.iife.js +3 -0
  38. package/dist/viewer/viewer.css +759 -0
  39. package/dist/viewer/viewer.iife.js +7243 -0
  40. package/package.json +28 -8
  41. package/dist/chunk-B2D5DMTO.js.map +0 -1
  42. package/dist/chunk-VQ4XMJTD.js +0 -107
  43. package/dist/chunk-VQ4XMJTD.js.map +0 -1
  44. package/dist/dot-exporter-3CVCH6J4.js +0 -8
  45. package/dist/petri-net-D-GN9g_D.d.ts +0 -570
  46. /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-WJMCJEFK.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,15 +1,22 @@
1
1
  import {
2
+ SmtVerifier,
2
3
  allPlaces,
3
4
  and,
4
5
  andPlaces,
5
6
  enumerateBranches,
6
7
  forwardInput,
8
+ isProven,
9
+ isViolated,
7
10
  outPlace,
8
11
  timeout,
9
12
  timeoutPlace,
10
13
  xor,
11
14
  xorPlaces
12
- } from "./chunk-VQ4XMJTD.js";
15
+ } from "./chunk-H62Z76FY.js";
16
+ import {
17
+ eventTransitionName,
18
+ isFailureEvent
19
+ } from "./chunk-SXK2Z45Z.js";
13
20
  import {
14
21
  MAX_DURATION_MS,
15
22
  deadline,
@@ -126,9 +133,10 @@ function consumptionCount(spec, available) {
126
133
 
127
134
  // src/core/transition-action.ts
128
135
  function passthrough() {
129
- return async () => {
130
- };
136
+ return PASSTHROUGH;
131
137
  }
138
+ var PASSTHROUGH = async () => {
139
+ };
132
140
  function transform(fn) {
133
141
  return async (ctx) => {
134
142
  const result = fn(ctx);
@@ -165,13 +173,13 @@ function produce(place2, value) {
165
173
  }
166
174
  function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
167
175
  return (ctx) => {
168
- return new Promise((resolve, reject) => {
176
+ return new Promise((resolve2, reject) => {
169
177
  let completed = false;
170
178
  const timer = setTimeout(() => {
171
179
  if (!completed) {
172
180
  completed = true;
173
181
  ctx.output(timeoutPlace2, timeoutValue);
174
- resolve();
182
+ resolve2();
175
183
  }
176
184
  }, timeoutMs);
177
185
  action(ctx).then(
@@ -179,7 +187,7 @@ function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
179
187
  if (!completed) {
180
188
  completed = true;
181
189
  clearTimeout(timer);
182
- resolve();
190
+ resolve2();
183
191
  }
184
192
  },
185
193
  (err) => {
@@ -627,6 +635,423 @@ function findForwardInputs(out) {
627
635
  }
628
636
  }
629
637
 
638
+ // src/core/compose-bindings.ts
639
+ var COMPOSE_BINDINGS_KEY = /* @__PURE__ */ Symbol("ComposeBindings.internal");
640
+ var ComposeBindings = class {
641
+ _portBindings = /* @__PURE__ */ new Map();
642
+ _channelBindings = /* @__PURE__ */ new Map();
643
+ /**
644
+ * @internal Use {@link PetriNetBuilder.compose} — instances are produced
645
+ * by the host builder and supplied to the caller's callback.
646
+ */
647
+ constructor(key) {
648
+ if (key !== COMPOSE_BINDINGS_KEY) {
649
+ throw new Error(
650
+ "Use PetriNetBuilder.compose(instance, b => ...) \u2014 ComposeBindings is not directly constructible"
651
+ );
652
+ }
653
+ }
654
+ /**
655
+ * Binds the named interface port to the given caller place per **MOD-020**.
656
+ *
657
+ * The port name is the **original** (pre-prefix) name declared in the
658
+ * subnet's `Interface`. The typed `<T>` parameter ensures the caller
659
+ * place's token type matches the interface port's token type at compile
660
+ * time per [MOD-022]; TypeScript does not validate the type at runtime
661
+ * because generics are erased.
662
+ *
663
+ * @throws when `portName` is already bound on this builder
664
+ */
665
+ bindPort(portName, callerPlace) {
666
+ if (this._portBindings.has(portName)) {
667
+ throw new Error(`Port '${portName}' is already bound`);
668
+ }
669
+ this._portBindings.set(portName, callerPlace);
670
+ return this;
671
+ }
672
+ /**
673
+ * Records a synchronous channel binding per **MOD-021**: at compose time,
674
+ * the instance-side renamed channel transition is merged with
675
+ * `callerTransition` into a single transition in the resulting flat net.
676
+ *
677
+ * **Status**: channel composition is implemented in task #13. Recording a
678
+ * channel binding here is permitted, but `compose(...)` currently raises
679
+ * an `Error` when any channel binding is present.
680
+ *
681
+ * @throws when `channelName` is already bound on this builder
682
+ */
683
+ bindChannel(channelName, callerTransition) {
684
+ if (this._channelBindings.has(channelName)) {
685
+ throw new Error(`Channel '${channelName}' is already bound`);
686
+ }
687
+ this._channelBindings.set(channelName, callerTransition);
688
+ return this;
689
+ }
690
+ /** Returns an unmodifiable view of the recorded port bindings. */
691
+ portBindings() {
692
+ return this._portBindings;
693
+ }
694
+ /** Returns an unmodifiable view of the recorded channel bindings. */
695
+ channelBindings() {
696
+ return this._channelBindings;
697
+ }
698
+ };
699
+ function __createComposeBindings() {
700
+ return new ComposeBindings(COMPOSE_BINDINGS_KEY);
701
+ }
702
+
703
+ // src/core/internal/subnet-rewriter.ts
704
+ function renamePlace(orig, prefix) {
705
+ return place(prefix + "/" + orig.name);
706
+ }
707
+ function renameNet(body, prefix, placeRemap, transitionRemap) {
708
+ placeRemap.clear();
709
+ transitionRemap.clear();
710
+ for (const orig of body.places) {
711
+ placeRemap.set(orig.name, renamePlace(orig, prefix));
712
+ }
713
+ const builder = PetriNet.builder(prefix + "/" + body.name);
714
+ for (const renamedPlace of placeRemap.values()) {
715
+ builder.place(renamedPlace);
716
+ }
717
+ for (const t of body.transitions) {
718
+ const renamed = rewriteTransition(t, prefix, placeRemap);
719
+ transitionRemap.set(t.name, renamed);
720
+ builder.transition(renamed);
721
+ }
722
+ return builder.build();
723
+ }
724
+ function rewriteTransition(t, prefix, placeRemap) {
725
+ return rebuildWithName(t, prefix + "/" + t.name, placeRemap);
726
+ }
727
+ function substitutePlaces(t, remap) {
728
+ return rebuildWithName(t, t.name, remap);
729
+ }
730
+ function rebuildWithName(t, name, remap) {
731
+ const builder = Transition.builder(name).timing(t.timing).priority(t.priority).action(t.action);
732
+ if (t.inputSpecs.length > 0) {
733
+ const rewrittenInputs = new Array(t.inputSpecs.length);
734
+ for (let i = 0; i < t.inputSpecs.length; i++) {
735
+ rewrittenInputs[i] = rewriteIn(t.inputSpecs[i], remap);
736
+ }
737
+ builder.inputs(...rewrittenInputs);
738
+ }
739
+ if (t.outputSpec !== null) {
740
+ builder.outputs(rewriteOut(t.outputSpec, remap));
741
+ }
742
+ for (let i = 0; i < t.inhibitors.length; i++) {
743
+ builder.inhibitor(rewriteInhibitor(t.inhibitors[i], remap).place);
744
+ }
745
+ for (let i = 0; i < t.reads.length; i++) {
746
+ builder.read(rewriteRead(t.reads[i], remap).place);
747
+ }
748
+ for (let i = 0; i < t.resets.length; i++) {
749
+ builder.reset(rewriteReset(t.resets[i], remap).place);
750
+ }
751
+ return builder.build();
752
+ }
753
+ function rewriteIn(spec, remap) {
754
+ switch (spec.type) {
755
+ case "one":
756
+ return one(resolve(spec.place, remap), spec.guard);
757
+ case "exactly":
758
+ return exactly(spec.count, resolve(spec.place, remap), spec.guard);
759
+ case "all":
760
+ return all(resolve(spec.place, remap), spec.guard);
761
+ case "at-least":
762
+ return atLeast(spec.minimum, resolve(spec.place, remap), spec.guard);
763
+ }
764
+ }
765
+ function rewriteOut(out, remap) {
766
+ switch (out.type) {
767
+ case "place":
768
+ return outPlace(resolve(out.place, remap));
769
+ case "forward-input":
770
+ return forwardInput(resolve(out.from, remap), resolve(out.to, remap));
771
+ case "and": {
772
+ const children = out.children;
773
+ const rewritten = new Array(children.length);
774
+ for (let i = 0; i < children.length; i++) {
775
+ rewritten[i] = rewriteOut(children[i], remap);
776
+ }
777
+ return { type: "and", children: rewritten };
778
+ }
779
+ case "xor": {
780
+ const children = out.children;
781
+ const rewritten = new Array(children.length);
782
+ for (let i = 0; i < children.length; i++) {
783
+ rewritten[i] = rewriteOut(children[i], remap);
784
+ }
785
+ return { type: "xor", children: rewritten };
786
+ }
787
+ case "timeout":
788
+ return timeout(out.afterMs, rewriteOut(out.child, remap));
789
+ }
790
+ }
791
+ function rewriteInhibitor(inh, remap) {
792
+ return { type: "inhibitor", place: resolve(inh.place, remap) };
793
+ }
794
+ function rewriteRead(rd, remap) {
795
+ return { type: "read", place: resolve(rd.place, remap) };
796
+ }
797
+ function rewriteReset(rs, remap) {
798
+ return { type: "reset", place: resolve(rs.place, remap) };
799
+ }
800
+ function resolve(p, remap) {
801
+ const replaced = remap.get(p.name);
802
+ return replaced !== void 0 ? replaced : p;
803
+ }
804
+ function mergeTransitions(caller, instance, mergedName) {
805
+ if (mergedName === void 0 || mergedName === null || mergedName.length === 0) {
806
+ throw new Error("mergeTransitions: mergedName must be a non-empty string");
807
+ }
808
+ const mergedTiming = mergeTimings(caller.timing, instance.timing, mergedName);
809
+ const mergedPriority = pickPriority(caller.priority, instance.priority);
810
+ const mergedAction = composeActions(caller.action, instance.action);
811
+ const builder = Transition.builder(mergedName).timing(mergedTiming).priority(mergedPriority);
812
+ if (mergedAction !== void 0) {
813
+ builder.action(mergedAction);
814
+ }
815
+ const unionedInputs = unionArcs(caller.inputSpecs, instance.inputSpecs, keyOfIn);
816
+ if (unionedInputs.length > 0) {
817
+ builder.inputs(...unionedInputs);
818
+ }
819
+ const mergedOutput = mergeOutputs(caller.outputSpec, instance.outputSpec);
820
+ if (mergedOutput !== null) {
821
+ builder.outputs(mergedOutput);
822
+ }
823
+ for (const inh of unionArcs(
824
+ caller.inhibitors,
825
+ instance.inhibitors,
826
+ keyOfInhibitor
827
+ )) {
828
+ builder.inhibitor(inh.place);
829
+ }
830
+ for (const rd of unionArcs(caller.reads, instance.reads, keyOfRead)) {
831
+ builder.read(rd.place);
832
+ }
833
+ for (const rs of unionArcs(caller.resets, instance.resets, keyOfReset)) {
834
+ builder.reset(rs.place);
835
+ }
836
+ return builder.build();
837
+ }
838
+ function mergeTimings(caller, instance, channelName) {
839
+ if (caller.type === "immediate" && instance.type === "immediate") {
840
+ return { type: "immediate" };
841
+ }
842
+ if (caller.type === "immediate") return instance;
843
+ if (instance.type === "immediate") return caller;
844
+ if (timingsEqual(caller, instance)) return caller;
845
+ throw new Error(
846
+ `Channel composition '${channelName}': conflicting non-Immediate timings \u2014 caller-side ${describeTiming(caller)} vs instance-side ${describeTiming(instance)}. Resolve explicitly by aligning the timings on either side (MOD-021).`
847
+ );
848
+ }
849
+ function pickPriority(callerPriority, _instancePriority) {
850
+ return callerPriority;
851
+ }
852
+ function composeActions(caller, instance) {
853
+ const callerIsPassthrough = caller === void 0 || isPassthrough(caller);
854
+ const instanceIsPassthrough = instance === void 0 || isPassthrough(instance);
855
+ if (callerIsPassthrough && instanceIsPassthrough) return void 0;
856
+ if (callerIsPassthrough) return instance;
857
+ if (instanceIsPassthrough) return caller;
858
+ return async (ctx) => {
859
+ await caller(ctx);
860
+ await instance(ctx);
861
+ };
862
+ }
863
+ function mergeOutputs(caller, instance) {
864
+ if (caller === null && instance === null) return null;
865
+ if (caller === null) return instance;
866
+ if (instance === null) return caller;
867
+ return and(caller, instance);
868
+ }
869
+ function unionArcs(caller, instance, keyOf) {
870
+ if (caller.length === 0 && instance.length === 0) return [];
871
+ const seen = /* @__PURE__ */ new Map();
872
+ for (let i2 = 0; i2 < caller.length; i2++) {
873
+ const arc = caller[i2];
874
+ const key = keyOf(arc);
875
+ if (!seen.has(key)) seen.set(key, arc);
876
+ }
877
+ for (let i2 = 0; i2 < instance.length; i2++) {
878
+ const arc = instance[i2];
879
+ const key = keyOf(arc);
880
+ if (!seen.has(key)) seen.set(key, arc);
881
+ }
882
+ const result = new Array(seen.size);
883
+ let i = 0;
884
+ for (const arc of seen.values()) result[i++] = arc;
885
+ return result;
886
+ }
887
+ function timingsEqual(a, b) {
888
+ if (a.type !== b.type) return false;
889
+ switch (a.type) {
890
+ case "immediate":
891
+ return true;
892
+ case "deadline":
893
+ return a.byMs === b.byMs;
894
+ case "delayed":
895
+ return a.afterMs === b.afterMs;
896
+ case "window": {
897
+ const w = b;
898
+ return a.earliestMs === w.earliestMs && a.latestMs === w.latestMs;
899
+ }
900
+ case "exact":
901
+ return a.atMs === b.atMs;
902
+ }
903
+ }
904
+ function describeTiming(t) {
905
+ switch (t.type) {
906
+ case "immediate":
907
+ return "Immediate";
908
+ case "deadline":
909
+ return `Deadline(byMs=${t.byMs})`;
910
+ case "delayed":
911
+ return `Delayed(afterMs=${t.afterMs})`;
912
+ case "window":
913
+ return `Window(earliestMs=${t.earliestMs}, latestMs=${t.latestMs})`;
914
+ case "exact":
915
+ return `Exact(atMs=${t.atMs})`;
916
+ }
917
+ }
918
+ var PASSTHROUGH_REF = passthrough();
919
+ function isPassthrough(action) {
920
+ return action === PASSTHROUGH_REF;
921
+ }
922
+ function keyOfIn(arc) {
923
+ switch (arc.type) {
924
+ case "one":
925
+ return arc.guard === void 0 ? `one|${arc.place.name}` : `one|${arc.place.name}|g:${guardKey(arc.guard)}`;
926
+ case "exactly":
927
+ return arc.guard === void 0 ? `exactly|${arc.place.name}|${arc.count}` : `exactly|${arc.place.name}|${arc.count}|g:${guardKey(arc.guard)}`;
928
+ case "all":
929
+ return arc.guard === void 0 ? `all|${arc.place.name}` : `all|${arc.place.name}|g:${guardKey(arc.guard)}`;
930
+ case "at-least":
931
+ return arc.guard === void 0 ? `atLeast|${arc.place.name}|${arc.minimum}` : `atLeast|${arc.place.name}|${arc.minimum}|g:${guardKey(arc.guard)}`;
932
+ }
933
+ }
934
+ function keyOfInhibitor(arc) {
935
+ return `inh|${arc.place.name}`;
936
+ }
937
+ function keyOfRead(arc) {
938
+ return `read|${arc.place.name}`;
939
+ }
940
+ function keyOfReset(arc) {
941
+ return `reset|${arc.place.name}`;
942
+ }
943
+ var GUARD_KEYS = /* @__PURE__ */ new WeakMap();
944
+ var guardKeyCounter = 0;
945
+ function guardKey(g) {
946
+ let k = GUARD_KEYS.get(g);
947
+ if (k === void 0) {
948
+ k = String(++guardKeyCounter);
949
+ GUARD_KEYS.set(g, k);
950
+ }
951
+ return k;
952
+ }
953
+ function applyFusion(transitions, fusionMap) {
954
+ const rewritten = /* @__PURE__ */ new Set();
955
+ for (const t of transitions) {
956
+ rewritten.add(substitutePlaces(t, fusionMap));
957
+ }
958
+ return rewritten;
959
+ }
960
+
961
+ // src/core/fusion-set.ts
962
+ var FUSION_SET_KEY = /* @__PURE__ */ Symbol("FusionSet.internal");
963
+ var FusionSet = class _FusionSet {
964
+ name;
965
+ members;
966
+ /** @internal Use {@link FusionSet.builder} or {@link FusionSet.of} to create instances. */
967
+ constructor(key, name, members) {
968
+ if (key !== FUSION_SET_KEY) {
969
+ throw new Error("Use FusionSet.builder() or FusionSet.of() to create instances");
970
+ }
971
+ this.name = name;
972
+ this.members = members;
973
+ }
974
+ /**
975
+ * Returns the canonical member — by convention, the first declared member.
976
+ * The canonical place's identity survives into the resulting flat net.
977
+ */
978
+ get canonical() {
979
+ return this.members[0];
980
+ }
981
+ /**
982
+ * Returns all members **except** the canonical member, in declaration order.
983
+ * These are the places that get substituted away at
984
+ * {@link import('./petri-net.js').PetriNetBuilder.build} time.
985
+ *
986
+ * For a single-member (degenerate) set, returns an empty array.
987
+ */
988
+ nonCanonical() {
989
+ if (this.members.length <= 1) return [];
990
+ return this.members.slice(1);
991
+ }
992
+ toString() {
993
+ return `FusionSet[${this.name}, canonical=${this.canonical.name}, members=${this.members.length}]`;
994
+ }
995
+ // ============================================================
996
+ // Static factories
997
+ // ============================================================
998
+ /** Returns a fresh {@link FusionSetBuilder} with the given human-readable name. */
999
+ static builder(name) {
1000
+ return new FusionSetBuilder(name);
1001
+ }
1002
+ /**
1003
+ * Convenience factory: builds a fusion set whose first member is `first`
1004
+ * (the canonical) and whose remaining members are `rest`, all sharing the
1005
+ * type `<T>`.
1006
+ *
1007
+ * The varargs form ensures static-type homogeneity at the call site (the
1008
+ * TypeScript compiler checks every `rest` entry is a `Place<T>`).
1009
+ */
1010
+ static of(name, first, ...rest) {
1011
+ const members = [first];
1012
+ for (const p of rest) members.push(p);
1013
+ return new _FusionSet(FUSION_SET_KEY, name, Object.freeze(members));
1014
+ }
1015
+ };
1016
+ var FusionSetBuilder = class {
1017
+ _name;
1018
+ _members = [];
1019
+ constructor(name) {
1020
+ if (typeof name !== "string" || name.length === 0) {
1021
+ throw new Error("FusionSet.builder: name must be a non-empty string");
1022
+ }
1023
+ this._name = name;
1024
+ }
1025
+ /**
1026
+ * Appends a member to the fusion set. The first member becomes the canonical
1027
+ * place per the convention documented on {@link FusionSet}.
1028
+ *
1029
+ * The `<T>` parameter is for compile-time guidance only: callers writing
1030
+ * typed code at the same call site benefit from the compiler checking
1031
+ * `Place<T>` at each member.
1032
+ */
1033
+ member(place2) {
1034
+ if (place2 === void 0 || place2 === null) {
1035
+ throw new Error(`FusionSet '${this._name}': member place must not be null`);
1036
+ }
1037
+ this._members.push(place2);
1038
+ return this;
1039
+ }
1040
+ /**
1041
+ * Builds the immutable {@link FusionSet}. Validates that the set has at
1042
+ * least one member; the empty set is rejected as malformed per **MOD-060**.
1043
+ * Single-member sets are accepted as a structurally degenerate no-op.
1044
+ */
1045
+ build() {
1046
+ if (this._members.length === 0) {
1047
+ throw new Error(
1048
+ `FusionSet '${this._name}': must have at least one member (MOD-060)`
1049
+ );
1050
+ }
1051
+ return new FusionSet(FUSION_SET_KEY, this._name, Object.freeze(this._members.slice()));
1052
+ }
1053
+ };
1054
+
630
1055
  // src/core/petri-net.ts
631
1056
  var PETRI_NET_KEY = /* @__PURE__ */ Symbol("PetriNet.internal");
632
1057
  var PetriNet = class _PetriNet {
@@ -673,6 +1098,7 @@ var PetriNetBuilder = class {
673
1098
  _name;
674
1099
  _places = /* @__PURE__ */ new Set();
675
1100
  _transitions = /* @__PURE__ */ new Set();
1101
+ _fusionSets = [];
676
1102
  constructor(name) {
677
1103
  this._name = name;
678
1104
  }
@@ -711,8 +1137,182 @@ var PetriNetBuilder = class {
711
1137
  for (const t of transitions) this.transition(t);
712
1138
  return this;
713
1139
  }
1140
+ compose(instance, arg) {
1141
+ if (typeof arg === "function") {
1142
+ const bindings = __createComposeBindings();
1143
+ arg(bindings);
1144
+ return this.composeInternal(instance, bindings.portBindings(), bindings.channelBindings());
1145
+ }
1146
+ const portMappings = arg instanceof Map ? arg : new Map(Object.entries(arg));
1147
+ return this.composeInternal(instance, portMappings, /* @__PURE__ */ new Map());
1148
+ }
1149
+ /**
1150
+ * @internal Shared compose implementation: validates port and channel
1151
+ * bindings, builds the place-substitution map, walks every renamed-body
1152
+ * transition through {@link substitutePlaces}, applies channel merges per
1153
+ * [MOD-021], and adds the resulting transitions to this builder.
1154
+ *
1155
+ * ## Channel-merge flow ([MOD-021])
1156
+ *
1157
+ * 1. Collect rewritten instance transitions into a working `Map<string,
1158
+ * Transition>` keyed by prefixed transition name (deferred — not yet
1159
+ * added to the builder's transition set).
1160
+ * 2. For each channel binding, resolve the renamed instance-side
1161
+ * transition through `instance.channel(channelName)`, then look up its
1162
+ * rewritten counterpart in the working map by name.
1163
+ * 3. Replace the working-map entry with a {@link mergeTransitions} result
1164
+ * that fuses caller-side + instance-side; remove the rewritten
1165
+ * instance-side entry. Also replace (or add) the caller-side
1166
+ * transition in this builder's transition set with the same merged
1167
+ * result, indexed under the caller's name slot.
1168
+ * 4. Add the surviving (un-merged) entries to this builder.
1169
+ *
1170
+ * The deferral matters: writing the rewritten instance transitions to the
1171
+ * builder eagerly would force a second "remove-then-replace" pass to
1172
+ * apply the channel merges, complicating the place-collection invariants.
1173
+ * Collecting first and merging second keeps the builder's transition set
1174
+ * finalized exactly once.
1175
+ *
1176
+ * Keying the working map by prefixed transition name (rather than by
1177
+ * Transition reference) is also robust against a prior
1178
+ * {@link Instance.bindActions} call that may have rebuilt the renamed-body
1179
+ * transitions, breaking identity equality between the body and the
1180
+ * channel-handle map — but the prefixed names remain stable.
1181
+ */
1182
+ composeInternal(instance, portMappings, channelBindings) {
1183
+ const iface = instance.def.iface;
1184
+ const mergeMap = /* @__PURE__ */ new Map();
1185
+ for (const [portName, callerPlace] of portMappings) {
1186
+ const port = iface.port(portName);
1187
+ if (port === void 0) {
1188
+ const knownPorts = [];
1189
+ for (const p of iface.ports.values()) knownPorts.push(p.name);
1190
+ throw new Error(
1191
+ `compose: no port named '${portName}' on subnet '${instance.def.name}' (instance prefix '${instance.prefix}'). Known ports: [${knownPorts.join(", ")}]`
1192
+ );
1193
+ }
1194
+ const ifacePlace = instance.port(portName);
1195
+ mergeMap.set(ifacePlace.name, callerPlace);
1196
+ }
1197
+ const rewrittenByName = /* @__PURE__ */ new Map();
1198
+ for (const t of instance.renamedBody.transitions) {
1199
+ rewrittenByName.set(t.name, substitutePlaces(t, mergeMap));
1200
+ }
1201
+ for (const [channelName, callerTrans] of channelBindings) {
1202
+ let instanceRenamedChannel;
1203
+ try {
1204
+ instanceRenamedChannel = instance.channel(channelName);
1205
+ } catch (cause) {
1206
+ const knownChannels = [];
1207
+ for (const c of iface.channels.values()) knownChannels.push(c.name);
1208
+ const err = new Error(
1209
+ `compose: no channel named '${channelName}' on subnet '${instance.def.name}' (instance prefix '${instance.prefix}'). Known channels: [${knownChannels.join(", ")}]`
1210
+ );
1211
+ err.cause = cause;
1212
+ throw err;
1213
+ }
1214
+ const rewrittenInstanceChannel = rewrittenByName.get(instanceRenamedChannel.name);
1215
+ if (rewrittenInstanceChannel === void 0) {
1216
+ throw new Error(
1217
+ `compose: channel '${channelName}' resolved to a transition '${instanceRenamedChannel.name}' that is not present in the renamed body. This indicates a SubnetDef invariant violation.`
1218
+ );
1219
+ }
1220
+ const merged = mergeTransitions(callerTrans, rewrittenInstanceChannel, callerTrans.name);
1221
+ rewrittenByName.delete(instanceRenamedChannel.name);
1222
+ this._transitions.delete(callerTrans);
1223
+ this.transition(merged);
1224
+ }
1225
+ for (const rewritten of rewrittenByName.values()) {
1226
+ this.transition(rewritten);
1227
+ }
1228
+ return this;
1229
+ }
1230
+ fuse(...args) {
1231
+ if (args.length === 1 && typeof args[0] === "function") {
1232
+ const declarer = args[0];
1233
+ const fb = FusionSet.builder(this._name + "-fusion");
1234
+ declarer(fb);
1235
+ this._fusionSets.push(fb.build());
1236
+ return this;
1237
+ }
1238
+ for (const s of args) {
1239
+ if (s === void 0 || s === null) {
1240
+ throw new Error("fuse: fusion set must not be null");
1241
+ }
1242
+ this._fusionSets.push(s);
1243
+ }
1244
+ return this;
1245
+ }
1246
+ /**
1247
+ * Builds the immutable {@link PetriNet}, applying fusion resolution (per
1248
+ * **MOD-061**) AFTER all transition/composition accumulation:
1249
+ *
1250
+ * 1. Detect overlapping fusion sets — a single place declared in two sets
1251
+ * is rejected with an `Error`.
1252
+ * 2. Build the `non-canonical → canonical` substitution map across all
1253
+ * sets, keyed by non-canonical place name (matching the rewriter's
1254
+ * Map<string, Place<unknown>> convention — TypeScript Place identity is
1255
+ * name-based per `runtime/compiled-net.ts`).
1256
+ * 3. Walk every transition through {@link applyFusion} to rewrite arc place
1257
+ * references.
1258
+ * 4. Re-derive the place set from the rewritten transitions plus any
1259
+ * caller-declared standalone places, dropping non-canonical members.
1260
+ * Caller-declared standalone places that happen to be non-canonical
1261
+ * members are also dropped.
1262
+ *
1263
+ * If no fusion sets were registered, the build is the trivial
1264
+ * `new PetriNet(...)` — the fusion machinery has no per-build cost when
1265
+ * unused.
1266
+ *
1267
+ * @throws when two fusion sets share a place
1268
+ */
714
1269
  build() {
715
- return new PetriNet(PETRI_NET_KEY, this._name, this._places, this._transitions);
1270
+ if (this._fusionSets.length === 0) {
1271
+ return new PetriNet(PETRI_NET_KEY, this._name, this._places, this._transitions);
1272
+ }
1273
+ return this.buildWithFusion();
1274
+ }
1275
+ /**
1276
+ * @internal Fusion-resolution pass per **MOD-061**. Split out from
1277
+ * {@link build} so the no-fusion fast path stays trivial.
1278
+ */
1279
+ buildWithFusion() {
1280
+ const ownership = /* @__PURE__ */ new Map();
1281
+ for (const set of this._fusionSets) {
1282
+ for (const member of set.members) {
1283
+ const prior = ownership.get(member.name);
1284
+ if (prior !== void 0 && prior !== set) {
1285
+ throw new Error(
1286
+ `Fusion overlap: place '${member.name}' appears in two fusion sets ('${prior.name}' and '${set.name}'). A place may appear in at most one fusion set (MOD-060).`
1287
+ );
1288
+ }
1289
+ ownership.set(member.name, set);
1290
+ }
1291
+ }
1292
+ const fusionMap = /* @__PURE__ */ new Map();
1293
+ const nonCanonicalNames = /* @__PURE__ */ new Set();
1294
+ for (const set of this._fusionSets) {
1295
+ const canonical = set.canonical;
1296
+ for (const nc of set.nonCanonical()) {
1297
+ fusionMap.set(nc.name, canonical);
1298
+ nonCanonicalNames.add(nc.name);
1299
+ }
1300
+ }
1301
+ const rewrittenTransitions = applyFusion(this._transitions, fusionMap);
1302
+ const rebuiltPlaces = /* @__PURE__ */ new Set();
1303
+ for (const p of this._places) {
1304
+ if (!nonCanonicalNames.has(p.name)) {
1305
+ rebuiltPlaces.add(p);
1306
+ }
1307
+ }
1308
+ for (const t of rewrittenTransitions) {
1309
+ for (const spec of t.inputSpecs) rebuiltPlaces.add(spec.place);
1310
+ for (const p of t.outputPlaces()) rebuiltPlaces.add(p);
1311
+ for (const inh of t.inhibitors) rebuiltPlaces.add(inh.place);
1312
+ for (const r of t.reads) rebuiltPlaces.add(r.place);
1313
+ for (const r of t.resets) rebuiltPlaces.add(r.place);
1314
+ }
1315
+ return new PetriNet(PETRI_NET_KEY, this._name, rebuiltPlaces, rewrittenTransitions);
716
1316
  }
717
1317
  };
718
1318
  function rebuildWithAction(t, action) {
@@ -735,6 +1335,602 @@ function rebuildWithAction(t, action) {
735
1335
  return builder.build();
736
1336
  }
737
1337
 
1338
+ // src/core/subnet.ts
1339
+ function closedSubnet(net) {
1340
+ return { kind: "closed", net };
1341
+ }
1342
+ function openSubnet(def) {
1343
+ return { kind: "open", def };
1344
+ }
1345
+
1346
+ // src/core/interface.ts
1347
+ var INTERFACE_KEY = /* @__PURE__ */ Symbol("Interface.internal");
1348
+ var Interface = class {
1349
+ ports;
1350
+ channels;
1351
+ /** @internal Use {@link Interface.builder} to create instances. */
1352
+ constructor(key, ports, channels) {
1353
+ if (key !== INTERFACE_KEY) throw new Error("Use Interface.builder() to create instances");
1354
+ this.ports = ports;
1355
+ this.channels = channels;
1356
+ }
1357
+ /** Looks up a port by name. Returns undefined when absent. */
1358
+ port(name) {
1359
+ return this.ports.get(name);
1360
+ }
1361
+ /** Looks up a channel by name. Returns undefined when absent. */
1362
+ channel(name) {
1363
+ return this.channels.get(name);
1364
+ }
1365
+ /**
1366
+ * Looks up a port by name and returns its underlying place, narrowed to
1367
+ * `Place<T>`. Returns undefined when the port does not exist.
1368
+ *
1369
+ * Note: TypeScript erases generics at runtime, so unlike Java's
1370
+ * `portPlaceAs(name, Class<T>)` this method cannot verify the token type at
1371
+ * runtime — the caller is trusted to supply the correct `T`. Per **MOD-022**,
1372
+ * type compatibility is enforced at compile time only in TypeScript.
1373
+ */
1374
+ placeAs(name) {
1375
+ const p = this.ports.get(name);
1376
+ if (p === void 0) return void 0;
1377
+ return p.place;
1378
+ }
1379
+ static builder() {
1380
+ return new InterfaceBuilder();
1381
+ }
1382
+ };
1383
+ var InterfaceBuilder = class {
1384
+ _ports = /* @__PURE__ */ new Map();
1385
+ _channels = /* @__PURE__ */ new Map();
1386
+ /** Add a pre-built port (rejects duplicate names). */
1387
+ port(port) {
1388
+ if (this._ports.has(port.name)) {
1389
+ throw new Error(`Duplicate port name: '${port.name}'`);
1390
+ }
1391
+ this._ports.set(port.name, port);
1392
+ return this;
1393
+ }
1394
+ /** Add an input port (advisory direction). */
1395
+ inputPort(name, place2) {
1396
+ return this.port({ name, direction: "input", place: place2 });
1397
+ }
1398
+ /** Add an output port (advisory direction). */
1399
+ outputPort(name, place2) {
1400
+ return this.port({ name, direction: "output", place: place2 });
1401
+ }
1402
+ /** Add an in-out port (advisory direction). */
1403
+ inoutPort(name, place2) {
1404
+ return this.port({ name, direction: "inout", place: place2 });
1405
+ }
1406
+ channel(channelOrName, transition) {
1407
+ const ch = typeof channelOrName === "string" ? { name: channelOrName, transition } : channelOrName;
1408
+ if (this._channels.has(ch.name)) {
1409
+ throw new Error(`Duplicate channel name: '${ch.name}'`);
1410
+ }
1411
+ this._channels.set(ch.name, ch);
1412
+ return this;
1413
+ }
1414
+ /** @internal Bulk-add ports already validated by the caller. */
1415
+ portsAll(ports) {
1416
+ for (const p of ports) this.port(p);
1417
+ return this;
1418
+ }
1419
+ /** @internal Bulk-add channels already validated by the caller. */
1420
+ channelsAll(channels) {
1421
+ for (const c of channels) this.channel(c);
1422
+ return this;
1423
+ }
1424
+ build() {
1425
+ return new Interface(
1426
+ INTERFACE_KEY,
1427
+ new Map(this._ports),
1428
+ new Map(this._channels)
1429
+ );
1430
+ }
1431
+ };
1432
+
1433
+ // src/core/instance.ts
1434
+ var INSTANCE_KEY = /* @__PURE__ */ Symbol("Instance.internal");
1435
+ var Instance = class {
1436
+ prefix;
1437
+ def;
1438
+ renamedBody;
1439
+ portHandles;
1440
+ channelHandles;
1441
+ params;
1442
+ /**
1443
+ * @internal Use {@link SubnetDef.instantiate} (or the internal factory
1444
+ * {@link __createInstance}) to create instances.
1445
+ */
1446
+ constructor(key, prefix, def, renamedBody, portHandles, channelHandles, params) {
1447
+ if (key !== INSTANCE_KEY) {
1448
+ throw new Error("Use SubnetDef.instantiate() to create Instance values");
1449
+ }
1450
+ this.prefix = prefix;
1451
+ this.def = def;
1452
+ this.renamedBody = renamedBody;
1453
+ this.portHandles = portHandles;
1454
+ this.channelHandles = channelHandles;
1455
+ this.params = params;
1456
+ }
1457
+ /**
1458
+ * Returns the renamed {@link Place} corresponding to the named port.
1459
+ *
1460
+ * The port name is the **original** (pre-prefix) name as declared in the
1461
+ * subnet's `Interface`. Per **MOD-022**, TypeScript enforces token-type
1462
+ * compatibility at compile time only; this method does not validate `T`
1463
+ * at runtime. A missing name raises an `Error`.
1464
+ *
1465
+ * @throws when the port name is unknown
1466
+ */
1467
+ port(name) {
1468
+ const p = this.portHandles.get(name);
1469
+ if (p === void 0) {
1470
+ throw new Error(`No port named '${name}' in instance '${this.prefix}'`);
1471
+ }
1472
+ return p;
1473
+ }
1474
+ /**
1475
+ * Returns the renamed {@link Transition} corresponding to the named channel.
1476
+ *
1477
+ * @throws when the channel name is unknown
1478
+ */
1479
+ channel(name) {
1480
+ const t = this.channelHandles.get(name);
1481
+ if (t === void 0) {
1482
+ throw new Error(`No channel named '${name}' in instance '${this.prefix}'`);
1483
+ }
1484
+ return t;
1485
+ }
1486
+ /**
1487
+ * Returns the debug-UI descriptor for this instance per **MOD-041**.
1488
+ */
1489
+ descriptor() {
1490
+ const transitions = [];
1491
+ for (const t of this.renamedBody.transitions) transitions.push(t.name);
1492
+ const exposedPlaces = [];
1493
+ for (const p of this.portHandles.values()) exposedPlaces.push(p.name);
1494
+ return {
1495
+ prefix: this.prefix,
1496
+ defName: this.def.name,
1497
+ transitions,
1498
+ exposedPlaces,
1499
+ params: this.params,
1500
+ parentPrefix: null
1501
+ };
1502
+ }
1503
+ /**
1504
+ * Produces a derived instance whose specified transitions (named by their
1505
+ * **original**, pre-prefix names) carry the supplied actions per **MOD-030**.
1506
+ *
1507
+ * For each entry `[originalName, action]`, the renamed body is searched for
1508
+ * the transition whose name equals `prefix + "/" + originalName`. The
1509
+ * resulting instance shares the original `def`, `prefix`, `params`, and
1510
+ * port/channel handle topology — only the renamed body is rebuilt with new
1511
+ * actions. Per **MOD-030**, calling `bindActions` on one instance does NOT
1512
+ * affect the actions held by other instances of the same `def`.
1513
+ *
1514
+ * Unrecognised original names raise an `Error` so typos surface eagerly.
1515
+ */
1516
+ bindActions(actionsByOriginalName) {
1517
+ const prefixedByName = /* @__PURE__ */ new Map();
1518
+ const renamedNames = /* @__PURE__ */ new Set();
1519
+ for (const t of this.renamedBody.transitions) {
1520
+ renamedNames.add(t.name);
1521
+ }
1522
+ for (const originalName of Object.keys(actionsByOriginalName)) {
1523
+ const prefixed = this.prefix + "/" + originalName;
1524
+ if (!renamedNames.has(prefixed)) {
1525
+ throw new Error(
1526
+ `Instance.bindActions: no transition '${originalName}' (resolved as '${prefixed}') in instance '${this.prefix}' of subnet '${this.def.name}'`
1527
+ );
1528
+ }
1529
+ prefixedByName.set(prefixed, actionsByOriginalName[originalName]);
1530
+ }
1531
+ const reboundBody = this.renamedBody.bindActionsWithResolver((name) => {
1532
+ const action = prefixedByName.get(name);
1533
+ if (action !== void 0) return action;
1534
+ for (const t of this.renamedBody.transitions) {
1535
+ if (t.name === name) return t.action;
1536
+ }
1537
+ throw new Error(`Instance.bindActions: resolver invoked with unknown name '${name}'`);
1538
+ });
1539
+ const reboundChannelHandles = /* @__PURE__ */ new Map();
1540
+ if (this.channelHandles.size > 0) {
1541
+ const byName = /* @__PURE__ */ new Map();
1542
+ for (const t of reboundBody.transitions) {
1543
+ byName.set(t.name, t);
1544
+ }
1545
+ for (const [name, oldT] of this.channelHandles) {
1546
+ const refreshed = byName.get(oldT.name);
1547
+ if (refreshed === void 0) {
1548
+ throw new Error(
1549
+ `Instance.bindActions: channel '${name}' transition '${oldT.name}' not found in rebound body`
1550
+ );
1551
+ }
1552
+ reboundChannelHandles.set(name, refreshed);
1553
+ }
1554
+ }
1555
+ return __createInstance(
1556
+ this.prefix,
1557
+ this.def,
1558
+ reboundBody,
1559
+ this.portHandles,
1560
+ reboundChannelHandles,
1561
+ this.params
1562
+ );
1563
+ }
1564
+ };
1565
+ function __createInstance(prefix, def, renamedBody, portHandles, channelHandles, params) {
1566
+ return new Instance(
1567
+ INSTANCE_KEY,
1568
+ prefix,
1569
+ def,
1570
+ renamedBody,
1571
+ portHandles,
1572
+ channelHandles,
1573
+ params
1574
+ );
1575
+ }
1576
+
1577
+ // src/verification/verification-harness.ts
1578
+ function buildVerificationResult(syntheticNet, perProperty) {
1579
+ const frozen = new Map(perProperty);
1580
+ return {
1581
+ syntheticNet,
1582
+ perProperty: frozen,
1583
+ allProven() {
1584
+ for (const r of frozen.values()) {
1585
+ if (!isProven(r)) return false;
1586
+ }
1587
+ return true;
1588
+ },
1589
+ anyViolated() {
1590
+ for (const r of frozen.values()) {
1591
+ if (isViolated(r)) return true;
1592
+ }
1593
+ return false;
1594
+ }
1595
+ };
1596
+ }
1597
+ function normaliseGenerators(generators) {
1598
+ if (generators instanceof Map) return new Map(generators);
1599
+ return new Map(Object.entries(generators));
1600
+ }
1601
+ function normaliseProperties(properties) {
1602
+ if (Array.isArray(properties)) return [...properties];
1603
+ return [...properties];
1604
+ }
1605
+
1606
+ // src/core/subnet-def.ts
1607
+ var SUBNET_DEF_KEY = /* @__PURE__ */ Symbol("SubnetDef.internal");
1608
+ var SubnetDef = class _SubnetDef {
1609
+ name;
1610
+ body;
1611
+ iface;
1612
+ /** @internal Use {@link SubnetDef.builder} or {@link SubnetDef.fromNet} to create instances. */
1613
+ constructor(key, name, body, iface) {
1614
+ if (key !== SUBNET_DEF_KEY) {
1615
+ throw new Error("Use SubnetDef.builder() or SubnetDef.fromNet() to create instances");
1616
+ }
1617
+ this.name = name;
1618
+ this.body = body;
1619
+ this.iface = iface;
1620
+ }
1621
+ /**
1622
+ * Produces a renamed module instance per **MOD-010**, **MOD-011**, **MOD-012**,
1623
+ * and **MOD-030**.
1624
+ *
1625
+ * The rename pass walks every place and transition of the body net,
1626
+ * substituting each name with `prefix + "/" + originalName`, and rebuilds
1627
+ * every arc with rewritten place references. Transition timing, priority,
1628
+ * and action are carried through by reference (action sharing per
1629
+ * [MOD-030]). The renamed body is itself a structurally valid `PetriNet`
1630
+ * per [CORE-040]; per-instance state isolation per [MOD-012] is a
1631
+ * structural consequence of distinct prefixed names.
1632
+ *
1633
+ * ## Prefix validation
1634
+ *
1635
+ * The `"/"` character is reserved as the prefix separator (per [MOD-010]).
1636
+ * User-supplied prefixes MUST NOT contain `"/"`; nested instantiation is
1637
+ * performed by the future `PetriNetBuilder.compose(...)` mechanism (per
1638
+ * [MOD-013]). A prefix containing `"/"` raises an `Error`.
1639
+ *
1640
+ * @param prefix the rename prefix (non-empty, must not contain `"/"`)
1641
+ * @param params the parameter value carried through to `Instance.params`
1642
+ * (may be omitted when `P` is `void`)
1643
+ * @throws when `prefix` is empty or contains `"/"`
1644
+ */
1645
+ instantiate(prefix, params) {
1646
+ validatePrefix(prefix);
1647
+ const placeRemap = /* @__PURE__ */ new Map();
1648
+ const transitionRemap = /* @__PURE__ */ new Map();
1649
+ const renamedBody = renameNet(this.body, prefix, placeRemap, transitionRemap);
1650
+ const portHandles = /* @__PURE__ */ new Map();
1651
+ for (const port of this.iface.ports.values()) {
1652
+ const renamed = placeRemap.get(port.place.name);
1653
+ if (renamed === void 0) {
1654
+ throw new Error(
1655
+ `Port '${port.name}' references place '${port.place.name}' that was not found in the renamed body. This indicates a SubnetDef invariant violation.`
1656
+ );
1657
+ }
1658
+ portHandles.set(port.name, renamed);
1659
+ }
1660
+ const channelHandles = /* @__PURE__ */ new Map();
1661
+ for (const channel of this.iface.channels.values()) {
1662
+ const renamed = transitionRemap.get(channel.transition.name);
1663
+ if (renamed === void 0) {
1664
+ throw new Error(
1665
+ `Channel '${channel.name}' references transition '${channel.transition.name}' that was not found in the renamed body. This indicates a SubnetDef invariant violation.`
1666
+ );
1667
+ }
1668
+ channelHandles.set(channel.name, renamed);
1669
+ }
1670
+ return __createInstance(
1671
+ prefix,
1672
+ this,
1673
+ renamedBody,
1674
+ portHandles,
1675
+ channelHandles,
1676
+ params
1677
+ );
1678
+ }
1679
+ /**
1680
+ * Verifies safety properties of this subnet definition **in isolation** per
1681
+ * **MOD-051**, by wrapping it in a synthetic enclosing net where each input
1682
+ * port is fed by an {@link EnvironmentPlace} (token-source per the harness
1683
+ * generator) and each output port is observed via a synthetic place. The
1684
+ * standard {@link SmtVerifier} (per [MOD-050]) is invoked once per property
1685
+ * declared in the harness; the resulting per-property outcomes are
1686
+ * aggregated into a {@link VerificationResult}.
1687
+ *
1688
+ * ## Synthetic-net construction
1689
+ *
1690
+ * The synthetic enclosing net is built by:
1691
+ * 1. Instantiating this `SubnetDef` with the prefix `"sut"` (system-under-test)
1692
+ * and `harness.params`.
1693
+ * 2. For each input or in-out port on the interface, looking up the
1694
+ * harness generator by port name, allocating a synthetic
1695
+ * {@link Place}`<unknown>` of the same conceptual token type as the
1696
+ * port, wrapping it in an {@link EnvironmentPlace}, and binding the
1697
+ * port to that synthetic place via
1698
+ * {@link import('./petri-net.js').PetriNetBuilder.compose}. The supplier
1699
+ * is invoked once at construction time to materialize the seed token —
1700
+ * its presence is what bounds the input behavior under analysis. **If
1701
+ * the harness map is missing a generator for a required input or in-out
1702
+ * port, an `Error` is thrown.**
1703
+ * 3. For each output or in-out port, allocating a synthetic observation
1704
+ * {@link Place}`<unknown>` and binding the port to it via the same
1705
+ * `compose(...)` call. The verifier inspects this place's reachability
1706
+ * / marking through the standard property APIs ({@link SmtProperty}).
1707
+ * 4. Building the resulting flat {@link PetriNet} per [MOD-023] (the
1708
+ * verifier is composition-unaware per [MOD-050]).
1709
+ *
1710
+ * ## Per-property invocation
1711
+ *
1712
+ * Each {@link SmtProperty} in the harness is verified independently against
1713
+ * the same synthetic net. The synthetic environment places are passed
1714
+ * through to the verifier so that places driven by the harness generators
1715
+ * are treated under the verifier's environment-analysis semantics rather
1716
+ * than as ordinary sink places.
1717
+ *
1718
+ * @param harness the verification harness — supplies parameters, input-port
1719
+ * token generators, and the property set
1720
+ * @returns a {@link VerificationResult} aggregating per-property
1721
+ * {@link SmtVerificationResult}s
1722
+ * @throws when an input or in-out port is missing a harness generator
1723
+ */
1724
+ async verify(harness) {
1725
+ if (harness === null || harness === void 0) {
1726
+ throw new Error("SubnetDef.verify: harness must not be null/undefined");
1727
+ }
1728
+ const sut = this.instantiate("sut", harness.params);
1729
+ const generators = normaliseGenerators(harness.portInputGenerators);
1730
+ const properties = normaliseProperties(harness.properties);
1731
+ const envPlaces = [];
1732
+ const portMappings = /* @__PURE__ */ new Map();
1733
+ for (const port of this.iface.ports.values()) {
1734
+ const portName = port.name;
1735
+ switch (port.direction) {
1736
+ case "input": {
1737
+ const generator = generators.get(portName);
1738
+ if (generator === void 0) {
1739
+ throw new Error(
1740
+ `verify: harness is missing an input generator for port '${portName}' on subnet '${this.name}' (MOD-051)`
1741
+ );
1742
+ }
1743
+ const seed = generator();
1744
+ if (seed === null || seed === void 0) {
1745
+ throw new Error(
1746
+ `verify: input generator for port '${portName}' on subnet '${this.name}' produced null/undefined`
1747
+ );
1748
+ }
1749
+ const synth = place(`harness_in_${portName}`);
1750
+ envPlaces.push(environmentPlace(synth.name));
1751
+ portMappings.set(portName, synth);
1752
+ break;
1753
+ }
1754
+ case "output": {
1755
+ const synth = place(`harness_out_${portName}`);
1756
+ portMappings.set(portName, synth);
1757
+ break;
1758
+ }
1759
+ case "inout": {
1760
+ const generator = generators.get(portName);
1761
+ if (generator === void 0) {
1762
+ throw new Error(
1763
+ `verify: harness is missing an input generator for in-out port '${portName}' on subnet '${this.name}' (MOD-051)`
1764
+ );
1765
+ }
1766
+ const seed = generator();
1767
+ if (seed === null || seed === void 0) {
1768
+ throw new Error(
1769
+ `verify: input generator for in-out port '${portName}' on subnet '${this.name}' produced null/undefined`
1770
+ );
1771
+ }
1772
+ const synth = place(`harness_io_${portName}`);
1773
+ envPlaces.push(environmentPlace(synth.name));
1774
+ portMappings.set(portName, synth);
1775
+ break;
1776
+ }
1777
+ }
1778
+ }
1779
+ const syntheticNet = PetriNet.builder("verify_" + this.name).compose(sut, portMappings).build();
1780
+ const perProperty = /* @__PURE__ */ new Map();
1781
+ for (const property of properties) {
1782
+ const verifier = SmtVerifier.forNet(syntheticNet).property(property);
1783
+ if (envPlaces.length > 0) {
1784
+ verifier.environmentPlaces(...envPlaces);
1785
+ }
1786
+ const result = await verifier.verify();
1787
+ perProperty.set(property, result);
1788
+ }
1789
+ return buildVerificationResult(syntheticNet, perProperty);
1790
+ }
1791
+ // ============================================================
1792
+ // Static factories
1793
+ // ============================================================
1794
+ static builder(name) {
1795
+ return new SubnetDefBuilder(name);
1796
+ }
1797
+ /**
1798
+ * Retrofit utility per **MOD-014**: wraps an existing closed {@link PetriNet}
1799
+ * plus an {@link Interface} into an unparameterised `SubnetDef<void>`.
1800
+ *
1801
+ * Validation per **MOD-014** / **MOD-006** is enforced before the result is
1802
+ * constructed:
1803
+ * - Every port's underlying `Place` must be present in `net.places`.
1804
+ * - Every channel's underlying `Transition` must be present in `net.transitions`.
1805
+ * - Port and channel name uniqueness is re-validated defensively (the
1806
+ * `Interface` builder already enforces this; hand-built `Interface`
1807
+ * values bypass that path).
1808
+ *
1809
+ * The resulting subnet definition is unparameterised (parameter type is `void`).
1810
+ *
1811
+ * @throws when a port place is not in `net.places`, a channel transition is
1812
+ * not in `net.transitions`, or port/channel names are not unique.
1813
+ */
1814
+ static fromNet(net, iface) {
1815
+ const bodyPlaces = net.places;
1816
+ const bodyTransitions = net.transitions;
1817
+ const seenPortNames = /* @__PURE__ */ new Set();
1818
+ for (const port of iface.ports.values()) {
1819
+ if (seenPortNames.has(port.name)) {
1820
+ throw new Error(
1821
+ `fromNet: duplicate port name '${port.name}' on interface for net '${net.name}' (MOD-006)`
1822
+ );
1823
+ }
1824
+ seenPortNames.add(port.name);
1825
+ if (!bodyPlaces.has(port.place)) {
1826
+ throw new Error(
1827
+ `fromNet: port '${port.name}' references place '${port.place.name}' which is not in net '${net.name}' (MOD-014/MOD-006)`
1828
+ );
1829
+ }
1830
+ }
1831
+ const seenChannelNames = /* @__PURE__ */ new Set();
1832
+ for (const channel of iface.channels.values()) {
1833
+ if (seenChannelNames.has(channel.name)) {
1834
+ throw new Error(
1835
+ `fromNet: duplicate channel name '${channel.name}' on interface for net '${net.name}' (MOD-006)`
1836
+ );
1837
+ }
1838
+ seenChannelNames.add(channel.name);
1839
+ if (!bodyTransitions.has(channel.transition)) {
1840
+ throw new Error(
1841
+ `fromNet: channel '${channel.name}' references transition '${channel.transition.name}' which is not in net '${net.name}' (MOD-014/MOD-006)`
1842
+ );
1843
+ }
1844
+ }
1845
+ return new _SubnetDef(SUBNET_DEF_KEY, net.name, net, iface);
1846
+ }
1847
+ };
1848
+ var SubnetDefBuilder = class {
1849
+ _name;
1850
+ _bodyBuilder;
1851
+ _ports = [];
1852
+ _channels = [];
1853
+ constructor(name) {
1854
+ this._name = name;
1855
+ this._bodyBuilder = PetriNet.builder(name);
1856
+ }
1857
+ // -------- body construction (delegates to PetriNet.Builder) --------
1858
+ transition(transition) {
1859
+ this._bodyBuilder.transition(transition);
1860
+ return this;
1861
+ }
1862
+ transitions(...transitions) {
1863
+ this._bodyBuilder.transitions(...transitions);
1864
+ return this;
1865
+ }
1866
+ place(place2) {
1867
+ this._bodyBuilder.place(place2);
1868
+ return this;
1869
+ }
1870
+ // -------- interface declarations --------
1871
+ inputPort(name, place2) {
1872
+ this._ports.push({ name, direction: "input", place: place2 });
1873
+ return this;
1874
+ }
1875
+ outputPort(name, place2) {
1876
+ this._ports.push({ name, direction: "output", place: place2 });
1877
+ return this;
1878
+ }
1879
+ inoutPort(name, place2) {
1880
+ this._ports.push({ name, direction: "inout", place: place2 });
1881
+ return this;
1882
+ }
1883
+ channel(name, transition) {
1884
+ this._channels.push({ name, transition });
1885
+ return this;
1886
+ }
1887
+ // -------- build & validate (MOD-006) --------
1888
+ build() {
1889
+ const built = this._bodyBuilder.build();
1890
+ const bodyPlaces = built.places;
1891
+ const bodyTransitions = built.transitions;
1892
+ const portNames = /* @__PURE__ */ new Set();
1893
+ for (const port of this._ports) {
1894
+ if (portNames.has(port.name)) {
1895
+ throw new Error(`Subnet '${this._name}': duplicate port name '${port.name}'`);
1896
+ }
1897
+ portNames.add(port.name);
1898
+ if (!bodyPlaces.has(port.place)) {
1899
+ throw new Error(
1900
+ `Subnet '${this._name}': port '${port.name}' references place '${port.place.name}' which is not in the body`
1901
+ );
1902
+ }
1903
+ }
1904
+ const channelNames = /* @__PURE__ */ new Set();
1905
+ for (const channel of this._channels) {
1906
+ if (channelNames.has(channel.name)) {
1907
+ throw new Error(`Subnet '${this._name}': duplicate channel name '${channel.name}'`);
1908
+ }
1909
+ channelNames.add(channel.name);
1910
+ if (!bodyTransitions.has(channel.transition)) {
1911
+ throw new Error(
1912
+ `Subnet '${this._name}': channel '${channel.name}' references transition '${channel.transition.name}' which is not in the body`
1913
+ );
1914
+ }
1915
+ }
1916
+ const ifaceBuilder = Interface.builder();
1917
+ ifaceBuilder.portsAll(this._ports);
1918
+ ifaceBuilder.channelsAll(this._channels);
1919
+ const iface = ifaceBuilder.build();
1920
+ return new SubnetDef(SUBNET_DEF_KEY, this._name, built, iface);
1921
+ }
1922
+ };
1923
+ function validatePrefix(prefix) {
1924
+ if (typeof prefix !== "string" || prefix.length === 0) {
1925
+ throw new Error("SubnetDef.instantiate: prefix must be a non-empty string");
1926
+ }
1927
+ if (prefix.indexOf("/") >= 0) {
1928
+ throw new Error(
1929
+ `SubnetDef.instantiate: prefix must not contain '/' (reserved as the prefix separator per MOD-010); use compose(...) for nested instantiation. Got: '${prefix}'`
1930
+ );
1931
+ }
1932
+ }
1933
+
738
1934
  // src/runtime/marking.ts
739
1935
  var EMPTY_TOKENS = Object.freeze([]);
740
1936
  var Marking = class _Marking {
@@ -1045,26 +2241,6 @@ function intersects(snapshot, mask) {
1045
2241
  return false;
1046
2242
  }
1047
2243
 
1048
- // src/event/net-event.ts
1049
- function eventTransitionName(event) {
1050
- switch (event.type) {
1051
- case "transition-enabled":
1052
- case "transition-clock-restarted":
1053
- case "transition-started":
1054
- case "transition-completed":
1055
- case "transition-failed":
1056
- case "transition-timed-out":
1057
- case "action-timed-out":
1058
- case "log-message":
1059
- return event.transitionName;
1060
- default:
1061
- return null;
1062
- }
1063
- }
1064
- function isFailureEvent(event) {
1065
- return event.type === "transition-failed" || event.type === "transition-timed-out" || event.type === "action-timed-out";
1066
- }
1067
-
1068
2244
  // src/event/event-store.ts
1069
2245
  function filterEvents(store, predicate) {
1070
2246
  return store.events().filter(predicate);
@@ -1346,11 +2522,11 @@ var BitmapNetExecutor = class {
1346
2522
  throw new Error(`Place ${envPlace.place.name} is not registered as an environment place`);
1347
2523
  }
1348
2524
  if (this.closed || this.draining) return false;
1349
- return new Promise((resolve, reject) => {
2525
+ return new Promise((resolve2, reject) => {
1350
2526
  this.externalQueue.push({
1351
2527
  place: envPlace.place,
1352
2528
  token,
1353
- resolve,
2529
+ resolve: resolve2,
1354
2530
  reject
1355
2531
  });
1356
2532
  this.wakeUp();
@@ -1832,8 +3008,8 @@ var BitmapNetExecutor = class {
1832
3008
  promises.push(Promise.race(arr));
1833
3009
  }
1834
3010
  if (!this.closed) {
1835
- promises.push(new Promise((resolve) => {
1836
- this.wakeUpResolve = resolve;
3011
+ promises.push(new Promise((resolve2) => {
3012
+ this.wakeUpResolve = resolve2;
1837
3013
  }));
1838
3014
  const timerMs = this.millisUntilNextTimedTransition();
1839
3015
  if (timerMs > 0 && timerMs < Infinity) {
@@ -2442,11 +3618,11 @@ var PrecompiledNetExecutor = class {
2442
3618
  throw new Error(`Place ${envPlace.place.name} is not registered as an environment place`);
2443
3619
  }
2444
3620
  if (this.closed || this.draining) return false;
2445
- return new Promise((resolve, reject) => {
3621
+ return new Promise((resolve2, reject) => {
2446
3622
  this.externalQueue.push({
2447
3623
  place: envPlace.place,
2448
3624
  token,
2449
- resolve,
3625
+ resolve: resolve2,
2450
3626
  reject
2451
3627
  });
2452
3628
  this.wakeUp();
@@ -3051,8 +4227,8 @@ var PrecompiledNetExecutor = class {
3051
4227
  }
3052
4228
  }
3053
4229
  if (!this.closed) {
3054
- promises.push(new Promise((resolve) => {
3055
- this.wakeUpResolve = resolve;
4230
+ promises.push(new Promise((resolve2) => {
4231
+ this.wakeUpResolve = resolve2;
3056
4232
  }));
3057
4233
  const timerMs = this.millisUntilNextTimedTransition();
3058
4234
  if (timerMs > 0 && timerMs < Infinity) {
@@ -3156,7 +4332,13 @@ var TimeoutSentinel2 = class extends Error {
3156
4332
  export {
3157
4333
  BitmapNetExecutor,
3158
4334
  CompiledNet,
4335
+ ComposeBindings,
4336
+ FusionSet,
4337
+ FusionSetBuilder,
3159
4338
  InMemoryEventStore,
4339
+ Instance,
4340
+ Interface,
4341
+ InterfaceBuilder,
3160
4342
  MAX_DURATION_MS,
3161
4343
  Marking,
3162
4344
  OutViolationError,
@@ -3164,6 +4346,8 @@ export {
3164
4346
  PetriNetBuilder,
3165
4347
  PrecompiledNet,
3166
4348
  PrecompiledNetExecutor,
4349
+ SubnetDef,
4350
+ SubnetDefBuilder,
3167
4351
  TokenInput,
3168
4352
  TokenOutput,
3169
4353
  Transition,
@@ -3176,6 +4360,7 @@ export {
3176
4360
  arcPlace,
3177
4361
  atLeast,
3178
4362
  clearBit,
4363
+ closedSubnet,
3179
4364
  consumptionCount,
3180
4365
  containsAll,
3181
4366
  deadline,
@@ -3204,6 +4389,7 @@ export {
3204
4389
  matchesGuard,
3205
4390
  noopEventStore,
3206
4391
  one,
4392
+ openSubnet,
3207
4393
  outPlace,
3208
4394
  outputArc,
3209
4395
  passthrough,