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.
- package/README.md +47 -0
- package/dist/{chunk-B2D5DMTO.js → chunk-B2CV5M3L.js} +228 -9
- package/dist/chunk-B2CV5M3L.js.map +1 -0
- package/dist/chunk-H62Z76FY.js +1346 -0
- package/dist/chunk-H62Z76FY.js.map +1 -0
- package/dist/chunk-RNWTYK5B.js +419 -0
- package/dist/chunk-RNWTYK5B.js.map +1 -0
- package/dist/chunk-SXK2Z45Z.js +50 -0
- package/dist/chunk-SXK2Z45Z.js.map +1 -0
- package/dist/debug/index.d.ts +50 -3
- package/dist/debug/index.js +64 -6
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +152 -31
- package/dist/doclet/index.js +458 -57
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +640 -7
- package/dist/doclet/resources/petrinet-diagrams.js +7238 -114
- package/dist/dot-exporter-WJMCJEFK.js +8 -0
- package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
- package/dist/export/index.d.ts +18 -4
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +41 -5
- package/dist/index.js +1221 -35
- package/dist/index.js.map +1 -1
- package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
- package/dist/render-QK57X4TP.js +73 -0
- package/dist/render-QK57X4TP.js.map +1 -0
- package/dist/verification/index.d.ts +3 -144
- package/dist/verification/index.js +30 -1214
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +227 -0
- package/dist/viewer/index.js +877 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/viewer/layout/index.d.ts +200 -0
- package/dist/viewer/layout/index.js +15 -0
- package/dist/viewer/layout/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +3 -0
- package/dist/viewer/viewer.css +759 -0
- package/dist/viewer/viewer.iife.js +7243 -0
- package/package.json +28 -8
- package/dist/chunk-B2D5DMTO.js.map +0 -1
- package/dist/chunk-VQ4XMJTD.js +0 -107
- package/dist/chunk-VQ4XMJTD.js.map +0 -1
- package/dist/dot-exporter-3CVCH6J4.js +0 -8
- package/dist/petri-net-D-GN9g_D.d.ts +0 -570
- /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-WJMCJEFK.js.map} +0 -0
|
@@ -0,0 +1,1461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An immutable token carrying a typed value through the Petri net.
|
|
3
|
+
*
|
|
4
|
+
* Tokens flow from place to place as transitions fire, carrying typed
|
|
5
|
+
* payloads that represent the state of a computation or workflow.
|
|
6
|
+
*/
|
|
7
|
+
interface Token<T> {
|
|
8
|
+
readonly value: T;
|
|
9
|
+
/** Epoch milliseconds when the token was created. */
|
|
10
|
+
readonly createdAt: number;
|
|
11
|
+
/**
|
|
12
|
+
* JSON-friendly projection of the token value, populated by
|
|
13
|
+
* {@link SessionArchiveReader} when hydrating a v3 archive so replay
|
|
14
|
+
* consumers see the same structured shape the writer emitted. Live tokens
|
|
15
|
+
* (produced by {@link tokenOf} / {@link tokenAt}) leave this `undefined`;
|
|
16
|
+
* the runtime ignores it. See [EVT-025](../../../spec/08-events-observability.md)
|
|
17
|
+
* AC5. (libpetri 1.8.0+)
|
|
18
|
+
*/
|
|
19
|
+
readonly structured?: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** Creates a token with the given value and current timestamp. */
|
|
22
|
+
declare function tokenOf<T>(value: T): Token<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Returns a unit token (marker with no meaningful value).
|
|
25
|
+
* Used for pure control flow where presence matters but data doesn't.
|
|
26
|
+
* Returns a cached singleton whose `value` is `null`.
|
|
27
|
+
*/
|
|
28
|
+
declare function unitToken(): Token<null>;
|
|
29
|
+
/** Creates a token with a specific timestamp (for testing/replay). */
|
|
30
|
+
declare function tokenAt<T>(value: T, createdAt: number): Token<T>;
|
|
31
|
+
/** Checks if this is the singleton unit token. */
|
|
32
|
+
declare function isUnit(token: Token<unknown>): boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A typed place in the Petri Net that holds tokens of a specific type.
|
|
36
|
+
*
|
|
37
|
+
* Places are the "state containers" of a Petri net. They hold tokens that
|
|
38
|
+
* represent data or resources flowing through the net.
|
|
39
|
+
*
|
|
40
|
+
* Places use name-based equality (matching Java record semantics).
|
|
41
|
+
* Internally use `Map<string, ...>` keyed by `place.name` for O(1) lookups.
|
|
42
|
+
*/
|
|
43
|
+
interface Place<T> {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
/** Phantom field to carry the type parameter. Never set at runtime. */
|
|
46
|
+
readonly _phantom?: T;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* An environment place that accepts external token injection.
|
|
50
|
+
* Wraps a regular Place and marks it for external event injection.
|
|
51
|
+
*/
|
|
52
|
+
interface EnvironmentPlace<T> {
|
|
53
|
+
readonly place: Place<T>;
|
|
54
|
+
}
|
|
55
|
+
/** Creates a typed place. */
|
|
56
|
+
declare function place<T>(name: string): Place<T>;
|
|
57
|
+
/** Creates an environment place (external event injection point). */
|
|
58
|
+
declare function environmentPlace<T>(name: string): EnvironmentPlace<T>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Arc types connecting places to transitions in the Petri net.
|
|
62
|
+
*
|
|
63
|
+
* | Arc Type | Requires Token? | Consumes? | Effect |
|
|
64
|
+
* |-----------|-----------------|-----------|--------------------------|
|
|
65
|
+
* | Input | Yes | Yes | Token consumed on fire |
|
|
66
|
+
* | Output | No | No | Token produced on complete|
|
|
67
|
+
* | Inhibitor | No (blocks) | No | Disables transition |
|
|
68
|
+
* | Read | Yes | No | Token remains |
|
|
69
|
+
* | Reset | No | Yes (all) | All tokens removed |
|
|
70
|
+
*/
|
|
71
|
+
type Arc = ArcInput | ArcOutput | ArcInhibitor | ArcRead | ArcReset;
|
|
72
|
+
interface ArcInput<T = any> {
|
|
73
|
+
readonly type: 'input';
|
|
74
|
+
readonly place: Place<T>;
|
|
75
|
+
readonly guard?: (value: T) => boolean;
|
|
76
|
+
}
|
|
77
|
+
interface ArcOutput<T = any> {
|
|
78
|
+
readonly type: 'output';
|
|
79
|
+
readonly place: Place<T>;
|
|
80
|
+
}
|
|
81
|
+
interface ArcInhibitor<T = any> {
|
|
82
|
+
readonly type: 'inhibitor';
|
|
83
|
+
readonly place: Place<T>;
|
|
84
|
+
}
|
|
85
|
+
interface ArcRead<T = any> {
|
|
86
|
+
readonly type: 'read';
|
|
87
|
+
readonly place: Place<T>;
|
|
88
|
+
}
|
|
89
|
+
interface ArcReset<T = any> {
|
|
90
|
+
readonly type: 'reset';
|
|
91
|
+
readonly place: Place<T>;
|
|
92
|
+
}
|
|
93
|
+
/** Input arc: consumes token from place when transition fires. */
|
|
94
|
+
declare function inputArc<T>(place: Place<T>, guard?: (value: T) => boolean): ArcInput<T>;
|
|
95
|
+
/** Output arc: produces token to place when transition fires. */
|
|
96
|
+
declare function outputArc<T>(place: Place<T>): ArcOutput<T>;
|
|
97
|
+
/** Inhibitor arc: blocks transition if place has tokens. */
|
|
98
|
+
declare function inhibitorArc<T>(place: Place<T>): ArcInhibitor<T>;
|
|
99
|
+
/** Read arc: requires token without consuming. */
|
|
100
|
+
declare function readArc<T>(place: Place<T>): ArcRead<T>;
|
|
101
|
+
/** Reset arc: removes all tokens from place when firing. */
|
|
102
|
+
declare function resetArc<T>(place: Place<T>): ArcReset<T>;
|
|
103
|
+
/** Returns the place this arc connects to. */
|
|
104
|
+
declare function arcPlace(arc: Arc): Place<any>;
|
|
105
|
+
/** Checks if an input arc has a guard predicate. */
|
|
106
|
+
declare function hasGuard(arc: ArcInput): boolean;
|
|
107
|
+
/** Checks if a token value matches an input arc's guard. */
|
|
108
|
+
declare function matchesGuard<T>(arc: ArcInput<T>, value: T): boolean;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Input specification with cardinality and optional guard predicate.
|
|
112
|
+
* CPN-compliant: cardinality determines how many tokens to consume,
|
|
113
|
+
* guard filters which tokens are eligible.
|
|
114
|
+
*
|
|
115
|
+
* Inputs are always AND-joined (all must be satisfied to enable transition).
|
|
116
|
+
* XOR on inputs is modeled via multiple transitions (conflict).
|
|
117
|
+
*/
|
|
118
|
+
type In = InOne | InExactly | InAll | InAtLeast;
|
|
119
|
+
interface InOne<T = any> {
|
|
120
|
+
readonly type: 'one';
|
|
121
|
+
readonly place: Place<T>;
|
|
122
|
+
readonly guard?: (value: T) => boolean;
|
|
123
|
+
}
|
|
124
|
+
interface InExactly<T = any> {
|
|
125
|
+
readonly type: 'exactly';
|
|
126
|
+
readonly place: Place<T>;
|
|
127
|
+
readonly count: number;
|
|
128
|
+
readonly guard?: (value: T) => boolean;
|
|
129
|
+
}
|
|
130
|
+
interface InAll<T = any> {
|
|
131
|
+
readonly type: 'all';
|
|
132
|
+
readonly place: Place<T>;
|
|
133
|
+
readonly guard?: (value: T) => boolean;
|
|
134
|
+
}
|
|
135
|
+
interface InAtLeast<T = any> {
|
|
136
|
+
readonly type: 'at-least';
|
|
137
|
+
readonly place: Place<T>;
|
|
138
|
+
readonly minimum: number;
|
|
139
|
+
readonly guard?: (value: T) => boolean;
|
|
140
|
+
}
|
|
141
|
+
/** Consume exactly 1 token (standard CPN semantics). Optional guard filters eligible tokens. */
|
|
142
|
+
declare function one<T>(place: Place<T>, guard?: (value: T) => boolean): InOne<T>;
|
|
143
|
+
/** Consume exactly N tokens (batching). Optional guard filters eligible tokens. */
|
|
144
|
+
declare function exactly<T>(count: number, place: Place<T>, guard?: (value: T) => boolean): InExactly<T>;
|
|
145
|
+
/** Consume all available tokens (must be 1+). Optional guard filters eligible tokens. */
|
|
146
|
+
declare function all<T>(place: Place<T>, guard?: (value: T) => boolean): InAll<T>;
|
|
147
|
+
/** Wait for N+ tokens, consume all when enabled. Optional guard filters eligible tokens. */
|
|
148
|
+
declare function atLeast<T>(minimum: number, place: Place<T>, guard?: (value: T) => boolean): InAtLeast<T>;
|
|
149
|
+
/** Returns the minimum number of tokens required to enable. */
|
|
150
|
+
declare function requiredCount(spec: In): number;
|
|
151
|
+
/**
|
|
152
|
+
* Returns the actual number of tokens to consume given the available count.
|
|
153
|
+
* - One: always consumes 1
|
|
154
|
+
* - Exactly: always consumes exactly count
|
|
155
|
+
* - All: consumes all available
|
|
156
|
+
* - AtLeast: consumes all available (when enabled, i.e., >= minimum)
|
|
157
|
+
*/
|
|
158
|
+
declare function consumptionCount(spec: In, available: number): number;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Output specification with explicit split semantics.
|
|
162
|
+
* Supports composite structures (XOR of ANDs, AND of XORs, etc.)
|
|
163
|
+
*
|
|
164
|
+
* - And: ALL children must receive tokens
|
|
165
|
+
* - Xor: EXACTLY ONE child receives token
|
|
166
|
+
* - Place: Leaf node representing a single output place
|
|
167
|
+
* - Timeout: Timeout branch that activates if action exceeds duration
|
|
168
|
+
* - ForwardInput: Forward consumed input to output on timeout
|
|
169
|
+
*/
|
|
170
|
+
type Out = OutAnd | OutXor | OutPlace | OutTimeout | OutForwardInput;
|
|
171
|
+
interface OutAnd {
|
|
172
|
+
readonly type: 'and';
|
|
173
|
+
readonly children: readonly Out[];
|
|
174
|
+
}
|
|
175
|
+
interface OutXor {
|
|
176
|
+
readonly type: 'xor';
|
|
177
|
+
readonly children: readonly Out[];
|
|
178
|
+
}
|
|
179
|
+
interface OutPlace {
|
|
180
|
+
readonly type: 'place';
|
|
181
|
+
readonly place: Place<any>;
|
|
182
|
+
}
|
|
183
|
+
interface OutTimeout {
|
|
184
|
+
readonly type: 'timeout';
|
|
185
|
+
/** Timeout duration in milliseconds. */
|
|
186
|
+
readonly afterMs: number;
|
|
187
|
+
readonly child: Out;
|
|
188
|
+
}
|
|
189
|
+
interface OutForwardInput {
|
|
190
|
+
readonly type: 'forward-input';
|
|
191
|
+
readonly from: Place<any>;
|
|
192
|
+
readonly to: Place<any>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* AND-split: all children must receive tokens.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* // AND of XOR branches: one of (A,B) AND one of (C,D)
|
|
200
|
+
* and(xorPlaces(placeA, placeB), xorPlaces(placeC, placeD))
|
|
201
|
+
*
|
|
202
|
+
* // AND with a fixed place + XOR branch
|
|
203
|
+
* and(outPlace(always), xorPlaces(left, right))
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
declare function and(...children: Out[]): OutAnd;
|
|
207
|
+
/** AND-split from places: all places must receive tokens. */
|
|
208
|
+
declare function andPlaces(...places: Place<any>[]): OutAnd;
|
|
209
|
+
/** XOR-split: exactly one child receives token. */
|
|
210
|
+
declare function xor(...children: Out[]): OutXor;
|
|
211
|
+
/** XOR-split from places: exactly one place receives token. */
|
|
212
|
+
declare function xorPlaces(...places: Place<any>[]): OutXor;
|
|
213
|
+
/** Leaf output spec for a single place. */
|
|
214
|
+
declare function outPlace(p: Place<any>): OutPlace;
|
|
215
|
+
/** Timeout output: activates if action exceeds duration. */
|
|
216
|
+
declare function timeout(afterMs: number, child: Out): OutTimeout;
|
|
217
|
+
/** Timeout output pointing to a single place. */
|
|
218
|
+
declare function timeoutPlace(afterMs: number, p: Place<any>): OutTimeout;
|
|
219
|
+
/** Forward consumed input value to output place on timeout. */
|
|
220
|
+
declare function forwardInput(from: Place<any>, to: Place<any>): OutForwardInput;
|
|
221
|
+
/** Collects all leaf places from this output spec (flattened). */
|
|
222
|
+
declare function allPlaces(out: Out): Set<Place<any>>;
|
|
223
|
+
/**
|
|
224
|
+
* Enumerates all possible output branches for structural analysis.
|
|
225
|
+
*
|
|
226
|
+
* - AND = single branch containing all child places (Cartesian product)
|
|
227
|
+
* - XOR = one branch per alternative child
|
|
228
|
+
* - Nested = Cartesian product for AND, union for XOR
|
|
229
|
+
*/
|
|
230
|
+
declare function enumerateBranches(out: Out): ReadonlyArray<ReadonlySet<Place<any>>>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Firing timing specification for transitions.
|
|
234
|
+
*
|
|
235
|
+
* Based on classical Time Petri Net (TPN) semantics:
|
|
236
|
+
* - Transition CANNOT fire before earliest time (lower bound)
|
|
237
|
+
* - Transition MUST fire by deadline OR become disabled (upper bound)
|
|
238
|
+
*
|
|
239
|
+
* All durations are in milliseconds.
|
|
240
|
+
*/
|
|
241
|
+
type Timing = TimingImmediate | TimingDeadline | TimingDelayed | TimingWindow | TimingExact;
|
|
242
|
+
interface TimingImmediate {
|
|
243
|
+
readonly type: 'immediate';
|
|
244
|
+
}
|
|
245
|
+
interface TimingDeadline {
|
|
246
|
+
readonly type: 'deadline';
|
|
247
|
+
/** Deadline in milliseconds. Must be positive. */
|
|
248
|
+
readonly byMs: number;
|
|
249
|
+
}
|
|
250
|
+
interface TimingDelayed {
|
|
251
|
+
readonly type: 'delayed';
|
|
252
|
+
/** Minimum delay in milliseconds. Must be non-negative. */
|
|
253
|
+
readonly afterMs: number;
|
|
254
|
+
}
|
|
255
|
+
interface TimingWindow {
|
|
256
|
+
readonly type: 'window';
|
|
257
|
+
/** Earliest firing time in milliseconds. Must be non-negative. */
|
|
258
|
+
readonly earliestMs: number;
|
|
259
|
+
/** Latest firing time in milliseconds. Must be >= earliestMs. */
|
|
260
|
+
readonly latestMs: number;
|
|
261
|
+
}
|
|
262
|
+
interface TimingExact {
|
|
263
|
+
readonly type: 'exact';
|
|
264
|
+
/** Exact firing time in milliseconds. Must be non-negative. */
|
|
265
|
+
readonly atMs: number;
|
|
266
|
+
}
|
|
267
|
+
/** ~100 years in milliseconds, used for "unconstrained" intervals. */
|
|
268
|
+
declare const MAX_DURATION_MS: number;
|
|
269
|
+
/** Immediate firing: can fire as soon as enabled, no deadline. [0, inf) */
|
|
270
|
+
declare function immediate(): TimingImmediate;
|
|
271
|
+
/** Immediate with deadline: can fire immediately, must fire by deadline. [0, by] */
|
|
272
|
+
declare function deadline(byMs: number): TimingDeadline;
|
|
273
|
+
/** Delayed firing: must wait, then can fire anytime. [after, inf) */
|
|
274
|
+
declare function delayed(afterMs: number): TimingDelayed;
|
|
275
|
+
/** Time window: can fire within [earliest, latest]. */
|
|
276
|
+
declare function window(earliestMs: number, latestMs: number): TimingWindow;
|
|
277
|
+
/** Exact timing: fires at precisely the specified time. [at, at] */
|
|
278
|
+
declare function exact(atMs: number): TimingExact;
|
|
279
|
+
/** Returns the earliest time (ms) the transition can fire after enabling. */
|
|
280
|
+
declare function earliest(timing: Timing): number;
|
|
281
|
+
/** Returns the latest time (ms) by which the transition must fire. */
|
|
282
|
+
declare function latest(timing: Timing): number;
|
|
283
|
+
/** Returns true if this timing has a finite deadline. */
|
|
284
|
+
declare function hasDeadline(timing: Timing): boolean;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Consumed input tokens bound to their source places.
|
|
288
|
+
*
|
|
289
|
+
* Passed to TransitionAction as the `input` parameter, providing
|
|
290
|
+
* type-safe read access to tokens consumed from input places.
|
|
291
|
+
*/
|
|
292
|
+
declare class TokenInput {
|
|
293
|
+
private readonly tokens;
|
|
294
|
+
/** Add a token (used by executor when firing transition). */
|
|
295
|
+
add<T>(place: Place<T>, token: Token<T>): this;
|
|
296
|
+
/** Get all tokens for a place. */
|
|
297
|
+
getAll<T>(place: Place<T>): readonly Token<T>[];
|
|
298
|
+
/** Get the first token for a place. Throws if no tokens. */
|
|
299
|
+
get<T>(place: Place<T>): Token<T>;
|
|
300
|
+
/** Get the first token's value for a place. Throws if no tokens. */
|
|
301
|
+
value<T>(place: Place<T>): T;
|
|
302
|
+
/** Get all token values for a place. */
|
|
303
|
+
values<T>(place: Place<T>): readonly T[];
|
|
304
|
+
/** Get token count for a place. */
|
|
305
|
+
count(place: Place<any>): number;
|
|
306
|
+
/** Check if any tokens exist for a place. */
|
|
307
|
+
has(place: Place<any>): boolean;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* An output entry: place + token pair.
|
|
312
|
+
*/
|
|
313
|
+
interface OutputEntry {
|
|
314
|
+
readonly place: Place<any>;
|
|
315
|
+
readonly token: Token<any>;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Collects output tokens produced by a transition action.
|
|
319
|
+
*/
|
|
320
|
+
declare class TokenOutput {
|
|
321
|
+
private readonly _entries;
|
|
322
|
+
/** Add a value to an output place (creates token with current timestamp). */
|
|
323
|
+
add<T>(place: Place<T>, value: T): this;
|
|
324
|
+
/** Add a pre-existing token to an output place. */
|
|
325
|
+
addToken<T>(place: Place<T>, token: Token<T>): this;
|
|
326
|
+
/** Returns all collected outputs. */
|
|
327
|
+
entries(): readonly OutputEntry[];
|
|
328
|
+
/** Check if any outputs were produced. */
|
|
329
|
+
isEmpty(): boolean;
|
|
330
|
+
/** Returns the set of place names that received tokens. */
|
|
331
|
+
placesWithTokens(): Set<string>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Callback for emitting log messages from transition actions. */
|
|
335
|
+
type LogFn = (level: string, message: string, error?: Error) => void;
|
|
336
|
+
/**
|
|
337
|
+
* Context provided to transition actions.
|
|
338
|
+
*
|
|
339
|
+
* Provides filtered access based on structure:
|
|
340
|
+
* - Input places (consumed tokens)
|
|
341
|
+
* - Read places (context tokens, not consumed)
|
|
342
|
+
* - Output places (where to produce tokens)
|
|
343
|
+
*
|
|
344
|
+
* Enforces the structure contract — actions can only access places
|
|
345
|
+
* declared in the transition's structure.
|
|
346
|
+
*/
|
|
347
|
+
declare class TransitionContext {
|
|
348
|
+
private readonly rawInput;
|
|
349
|
+
private readonly _rawOutput;
|
|
350
|
+
private readonly allowedInputs;
|
|
351
|
+
private readonly allowedReads;
|
|
352
|
+
private readonly allowedOutputs;
|
|
353
|
+
private readonly _inputPlaces;
|
|
354
|
+
private readonly _readPlaces;
|
|
355
|
+
private readonly _outputPlaces;
|
|
356
|
+
private readonly _transitionName;
|
|
357
|
+
private readonly executionCtx;
|
|
358
|
+
private readonly _logFn?;
|
|
359
|
+
constructor(transitionName: string, rawInput: TokenInput, rawOutput: TokenOutput, inputPlaces: ReadonlySet<Place<any>>, readPlaces: ReadonlySet<Place<any>>, outputPlaces: ReadonlySet<Place<any>>, executionContext?: Map<string, unknown>, logFn?: LogFn);
|
|
360
|
+
/** Get single consumed input value. Throws if place not declared or multiple tokens. */
|
|
361
|
+
input<T>(place: Place<T>): T;
|
|
362
|
+
/** Get all consumed input values for a place. */
|
|
363
|
+
inputs<T>(place: Place<T>): readonly T[];
|
|
364
|
+
/** Get consumed input token with metadata. */
|
|
365
|
+
inputToken<T>(place: Place<T>): Token<T>;
|
|
366
|
+
/** Returns declared input places (consumed). */
|
|
367
|
+
inputPlaces(): ReadonlySet<Place<any>>;
|
|
368
|
+
private requireInput;
|
|
369
|
+
/** Get read-only context value. Throws if place not declared as read. */
|
|
370
|
+
read<T>(place: Place<T>): T;
|
|
371
|
+
/** Get all read-only context values for a place. */
|
|
372
|
+
reads<T>(place: Place<T>): readonly T[];
|
|
373
|
+
/** Returns declared read places (context, not consumed). */
|
|
374
|
+
readPlaces(): ReadonlySet<Place<any>>;
|
|
375
|
+
private requireRead;
|
|
376
|
+
/**
|
|
377
|
+
* Add one or more output values to the same place in a single call.
|
|
378
|
+
*
|
|
379
|
+
* Validates the place once, then appends each value to the output
|
|
380
|
+
* collector. Calling with zero values is a no-op.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ctx.output(outPlace, 'a', 'b', 'c');
|
|
384
|
+
* ctx.output(outPlace, ...someArray);
|
|
385
|
+
*
|
|
386
|
+
* @throws if place not declared as output.
|
|
387
|
+
*/
|
|
388
|
+
output<T>(place: Place<T>, ...values: T[]): this;
|
|
389
|
+
/**
|
|
390
|
+
* Add one or more pre-built output tokens to the same place in a single call.
|
|
391
|
+
*
|
|
392
|
+
* Validates the place once, then appends each token. Calling with zero
|
|
393
|
+
* tokens is a no-op.
|
|
394
|
+
*
|
|
395
|
+
* @throws if place not declared as output.
|
|
396
|
+
*/
|
|
397
|
+
outputToken<T>(place: Place<T>, ...tokens: Token<T>[]): this;
|
|
398
|
+
/** Returns declared output places. */
|
|
399
|
+
outputPlaces(): ReadonlySet<Place<any>>;
|
|
400
|
+
private requireOutput;
|
|
401
|
+
/** Returns the transition name. */
|
|
402
|
+
transitionName(): string;
|
|
403
|
+
/** Retrieves an execution context object by key. */
|
|
404
|
+
executionContext<T>(key: string): T | undefined;
|
|
405
|
+
/** Checks if an execution context object of the given key is present. */
|
|
406
|
+
hasExecutionContext(key: string): boolean;
|
|
407
|
+
/** Emits a structured log message into the event store. */
|
|
408
|
+
log(level: string, message: string, error?: Error): void;
|
|
409
|
+
/** @internal Used by BitmapNetExecutor to collect outputs after action completion. */
|
|
410
|
+
rawOutput(): TokenOutput;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The action executed when a transition fires.
|
|
415
|
+
* Receives a TransitionContext providing filtered I/O and structure access.
|
|
416
|
+
*/
|
|
417
|
+
type TransitionAction = (ctx: TransitionContext) => Promise<void>;
|
|
418
|
+
/**
|
|
419
|
+
* Identity action: produces no outputs.
|
|
420
|
+
*
|
|
421
|
+
* Returns a stable singleton reference (cached on first call). Reference
|
|
422
|
+
* stability is relied on by {@link import('./internal/subnet-rewriter.js')
|
|
423
|
+
* .composeActions} during channel composition (MOD-021) to short-circuit a
|
|
424
|
+
* passthrough-on-both-sides merge to passthrough — saving a microtask hop
|
|
425
|
+
* and matching the Java implementation's behaviour where both transitions'
|
|
426
|
+
* default actions collapse to the builder's own passthrough default.
|
|
427
|
+
*/
|
|
428
|
+
declare function passthrough(): TransitionAction;
|
|
429
|
+
/**
|
|
430
|
+
* Transform action: applies function to context, copies result to ALL output places.
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* ```ts
|
|
434
|
+
* const action = transform(ctx => ctx.input(inputPlace).toUpperCase());
|
|
435
|
+
* // Result is copied to every declared output place
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
declare function transform(fn: (ctx: TransitionContext) => unknown): TransitionAction;
|
|
439
|
+
/**
|
|
440
|
+
* Fork action: copies single input token to all outputs.
|
|
441
|
+
* Requires exactly one input place (derived from structure).
|
|
442
|
+
*/
|
|
443
|
+
declare function fork(): TransitionAction;
|
|
444
|
+
/**
|
|
445
|
+
* Transform with explicit input place.
|
|
446
|
+
*/
|
|
447
|
+
declare function transformFrom<I>(inputPlace: Place<I>, fn: (value: I) => unknown): TransitionAction;
|
|
448
|
+
/**
|
|
449
|
+
* Async transform: applies async function, copies result to all outputs.
|
|
450
|
+
*/
|
|
451
|
+
declare function transformAsync(fn: (ctx: TransitionContext) => Promise<unknown>): TransitionAction;
|
|
452
|
+
/** Produce action: produces a single token with the given value to the specified place. */
|
|
453
|
+
declare function produce<T>(place: Place<T>, value: T): TransitionAction;
|
|
454
|
+
/**
|
|
455
|
+
* Wraps an action with timeout handling.
|
|
456
|
+
* If the action completes within the timeout, normal completion.
|
|
457
|
+
* If the timeout expires, the timeoutValue is produced to the timeoutPlace.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* const action = withTimeout(
|
|
462
|
+
* async (ctx) => { ctx.output(resultPlace, await fetchData()); },
|
|
463
|
+
* 5000,
|
|
464
|
+
* timeoutPlace,
|
|
465
|
+
* 'timed-out',
|
|
466
|
+
* );
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
declare function withTimeout<T>(action: TransitionAction, timeoutMs: number, timeoutPlace: Place<T>, timeoutValue: T): TransitionAction;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* A transition in the Time Petri Net that transforms tokens.
|
|
473
|
+
*
|
|
474
|
+
* Transitions use identity-based equality (===) — each instance is unique
|
|
475
|
+
* regardless of name. The name is purely a label for display/debugging/export.
|
|
476
|
+
*/
|
|
477
|
+
declare class Transition {
|
|
478
|
+
readonly name: string;
|
|
479
|
+
readonly inputSpecs: readonly In[];
|
|
480
|
+
readonly outputSpec: Out | null;
|
|
481
|
+
readonly inhibitors: readonly ArcInhibitor[];
|
|
482
|
+
readonly reads: readonly ArcRead[];
|
|
483
|
+
readonly resets: readonly ArcReset[];
|
|
484
|
+
readonly timing: Timing;
|
|
485
|
+
readonly actionTimeout: OutTimeout | null;
|
|
486
|
+
readonly action: TransitionAction;
|
|
487
|
+
readonly priority: number;
|
|
488
|
+
private readonly _inputPlaces;
|
|
489
|
+
private readonly _readPlaces;
|
|
490
|
+
private readonly _outputPlaces;
|
|
491
|
+
/** @internal Use {@link Transition.builder} to create instances. */
|
|
492
|
+
constructor(key: symbol, name: string, inputSpecs: readonly In[], outputSpec: Out | null, inhibitors: readonly ArcInhibitor[], reads: readonly ArcRead[], resets: readonly ArcReset[], timing: Timing, action: TransitionAction, priority: number);
|
|
493
|
+
/** Returns set of input places — consumed tokens. */
|
|
494
|
+
inputPlaces(): ReadonlySet<Place<any>>;
|
|
495
|
+
/** Returns set of read places — context tokens, not consumed. */
|
|
496
|
+
readPlaces(): ReadonlySet<Place<any>>;
|
|
497
|
+
/** Returns set of output places — where tokens are produced. */
|
|
498
|
+
outputPlaces(): ReadonlySet<Place<any>>;
|
|
499
|
+
/** Returns true if this transition has an action timeout. */
|
|
500
|
+
hasActionTimeout(): boolean;
|
|
501
|
+
toString(): string;
|
|
502
|
+
static builder(name: string): TransitionBuilder;
|
|
503
|
+
}
|
|
504
|
+
declare class TransitionBuilder {
|
|
505
|
+
private readonly _name;
|
|
506
|
+
private readonly _inputSpecs;
|
|
507
|
+
private _outputSpec;
|
|
508
|
+
private readonly _inhibitors;
|
|
509
|
+
private readonly _reads;
|
|
510
|
+
private readonly _resets;
|
|
511
|
+
private _timing;
|
|
512
|
+
private _action;
|
|
513
|
+
private _priority;
|
|
514
|
+
constructor(name: string);
|
|
515
|
+
/** Add input specifications with cardinality. */
|
|
516
|
+
inputs(...specs: In[]): this;
|
|
517
|
+
/** Set the output specification (composite AND/XOR structure). */
|
|
518
|
+
outputs(spec: Out): this;
|
|
519
|
+
/** Add inhibitor arc. */
|
|
520
|
+
inhibitor(place: Place<any>): this;
|
|
521
|
+
/** Add inhibitor arcs. */
|
|
522
|
+
inhibitors(...places: Place<any>[]): this;
|
|
523
|
+
/** Add read arc. */
|
|
524
|
+
read(place: Place<any>): this;
|
|
525
|
+
/** Add read arcs. */
|
|
526
|
+
reads(...places: Place<any>[]): this;
|
|
527
|
+
/** Add reset arc. */
|
|
528
|
+
reset(place: Place<any>): this;
|
|
529
|
+
/** Add reset arcs. */
|
|
530
|
+
resets(...places: Place<any>[]): this;
|
|
531
|
+
/** Set timing specification. */
|
|
532
|
+
timing(timing: Timing): this;
|
|
533
|
+
/** Set the transition action. */
|
|
534
|
+
action(action: TransitionAction): this;
|
|
535
|
+
/** Set the priority (higher fires first). */
|
|
536
|
+
priority(priority: number): this;
|
|
537
|
+
build(): Transition;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Advisory direction metadata for an interface place.
|
|
542
|
+
*
|
|
543
|
+
* Per **MOD-004**, direction is metadata only — it does NOT constrain arc flow
|
|
544
|
+
* at runtime. Token movement is governed by arcs declared per CORE-030..CORE-035.
|
|
545
|
+
*/
|
|
546
|
+
type PortDirection = 'input' | 'output' | 'inout';
|
|
547
|
+
/**
|
|
548
|
+
* A typed interface place exposed for composition, per **MOD-003**.
|
|
549
|
+
*
|
|
550
|
+
* Direction is advisory metadata only (per **MOD-004**); the underlying
|
|
551
|
+
* `Place<T>` reference is what is rewired at compose time.
|
|
552
|
+
*/
|
|
553
|
+
interface Port<T = unknown> {
|
|
554
|
+
/** Port name, unique within the {@link Interface}. */
|
|
555
|
+
readonly name: string;
|
|
556
|
+
/** Direction (advisory). */
|
|
557
|
+
readonly direction: PortDirection;
|
|
558
|
+
/** The body place exposed by this port. */
|
|
559
|
+
readonly place: Place<T>;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* An interface transition exposed for synchronous fusion with a caller-side
|
|
563
|
+
* transition, per **MOD-005**.
|
|
564
|
+
*
|
|
565
|
+
* The only present variant in this scaffolding is the synchronous channel.
|
|
566
|
+
* The interface is left structurally open for future channel kinds without
|
|
567
|
+
* breaking existing pattern matches on `name`/`transition`.
|
|
568
|
+
*/
|
|
569
|
+
interface Channel {
|
|
570
|
+
/** Channel name, unique within the channel namespace. */
|
|
571
|
+
readonly name: string;
|
|
572
|
+
/** The body transition exposed by this channel. */
|
|
573
|
+
readonly transition: Transition;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Immutable declaration of a subnet's **interface**: the set of {@link Port}
|
|
577
|
+
* (interface places) and {@link Channel} (interface transitions) exposed for
|
|
578
|
+
* composition with an enclosing net.
|
|
579
|
+
*
|
|
580
|
+
* Specified by `spec/11-modular-composition.md` requirements **MOD-003**
|
|
581
|
+
* (port declaration) and **MOD-005** (channel declaration). Validation rules
|
|
582
|
+
* per **MOD-006** are enforced by `SubnetDef.builder()` at subnet build
|
|
583
|
+
* time; this class itself enforces only port/channel name uniqueness within
|
|
584
|
+
* its respective namespaces (used both by `SubnetDef` and by hand-built
|
|
585
|
+
* interfaces fed into `SubnetDef.fromNet`).
|
|
586
|
+
*/
|
|
587
|
+
declare class Interface {
|
|
588
|
+
readonly ports: ReadonlyMap<string, Port<unknown>>;
|
|
589
|
+
readonly channels: ReadonlyMap<string, Channel>;
|
|
590
|
+
/** @internal Use {@link Interface.builder} to create instances. */
|
|
591
|
+
constructor(key: symbol, ports: ReadonlyMap<string, Port<unknown>>, channels: ReadonlyMap<string, Channel>);
|
|
592
|
+
/** Looks up a port by name. Returns undefined when absent. */
|
|
593
|
+
port<T = unknown>(name: string): Port<T> | undefined;
|
|
594
|
+
/** Looks up a channel by name. Returns undefined when absent. */
|
|
595
|
+
channel(name: string): Channel | undefined;
|
|
596
|
+
/**
|
|
597
|
+
* Looks up a port by name and returns its underlying place, narrowed to
|
|
598
|
+
* `Place<T>`. Returns undefined when the port does not exist.
|
|
599
|
+
*
|
|
600
|
+
* Note: TypeScript erases generics at runtime, so unlike Java's
|
|
601
|
+
* `portPlaceAs(name, Class<T>)` this method cannot verify the token type at
|
|
602
|
+
* runtime — the caller is trusted to supply the correct `T`. Per **MOD-022**,
|
|
603
|
+
* type compatibility is enforced at compile time only in TypeScript.
|
|
604
|
+
*/
|
|
605
|
+
placeAs<T>(name: string): Place<T> | undefined;
|
|
606
|
+
static builder(): InterfaceBuilder;
|
|
607
|
+
}
|
|
608
|
+
declare class InterfaceBuilder {
|
|
609
|
+
private readonly _ports;
|
|
610
|
+
private readonly _channels;
|
|
611
|
+
/** Add a pre-built port (rejects duplicate names). */
|
|
612
|
+
port(port: Port<unknown>): this;
|
|
613
|
+
/** Add an input port (advisory direction). */
|
|
614
|
+
inputPort<T>(name: string, place: Place<T>): this;
|
|
615
|
+
/** Add an output port (advisory direction). */
|
|
616
|
+
outputPort<T>(name: string, place: Place<T>): this;
|
|
617
|
+
/** Add an in-out port (advisory direction). */
|
|
618
|
+
inoutPort<T>(name: string, place: Place<T>): this;
|
|
619
|
+
/** Add a pre-built channel (rejects duplicate names). */
|
|
620
|
+
channel(channel: Channel): this;
|
|
621
|
+
/** Declare a synchronous channel by name + transition (rejects duplicate names). */
|
|
622
|
+
channel(name: string, transition: Transition): this;
|
|
623
|
+
/** @internal Bulk-add ports already validated by the caller. */
|
|
624
|
+
portsAll(ports: Iterable<Port<unknown>>): this;
|
|
625
|
+
/** @internal Bulk-add channels already validated by the caller. */
|
|
626
|
+
channelsAll(channels: Iterable<Channel>): this;
|
|
627
|
+
build(): Interface;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Safety properties that can be verified via IC3/PDR.
|
|
632
|
+
*
|
|
633
|
+
* Each property is encoded as an error condition: if a reachable state
|
|
634
|
+
* violates the property, Spacer finds a counterexample. If no violation
|
|
635
|
+
* is reachable, the property is proven.
|
|
636
|
+
*/
|
|
637
|
+
type SmtProperty = DeadlockFree | MutualExclusion | PlaceBound | Unreachable;
|
|
638
|
+
/** Deadlock-freedom: no reachable marking has all transitions disabled. */
|
|
639
|
+
interface DeadlockFree {
|
|
640
|
+
readonly type: 'deadlock-free';
|
|
641
|
+
}
|
|
642
|
+
/** Mutual exclusion: two places never have tokens simultaneously. */
|
|
643
|
+
interface MutualExclusion {
|
|
644
|
+
readonly type: 'mutual-exclusion';
|
|
645
|
+
readonly p1: Place<any>;
|
|
646
|
+
readonly p2: Place<any>;
|
|
647
|
+
}
|
|
648
|
+
/** Place bound: a place never exceeds a given token count. */
|
|
649
|
+
interface PlaceBound {
|
|
650
|
+
readonly type: 'place-bound';
|
|
651
|
+
readonly place: Place<any>;
|
|
652
|
+
readonly bound: number;
|
|
653
|
+
}
|
|
654
|
+
/** Unreachability: the given places never all have tokens simultaneously. */
|
|
655
|
+
interface Unreachable {
|
|
656
|
+
readonly type: 'unreachable';
|
|
657
|
+
readonly places: ReadonlySet<Place<any>>;
|
|
658
|
+
}
|
|
659
|
+
declare function deadlockFree(): DeadlockFree;
|
|
660
|
+
declare function mutualExclusion(p1: Place<any>, p2: Place<any>): MutualExclusion;
|
|
661
|
+
declare function placeBound(place: Place<any>, bound: number): PlaceBound;
|
|
662
|
+
declare function unreachable(places: ReadonlySet<Place<any>>): Unreachable;
|
|
663
|
+
/** Human-readable description of a property. */
|
|
664
|
+
declare function propertyDescription(prop: SmtProperty): string;
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Immutable snapshot of a Petri net marking for state space analysis.
|
|
668
|
+
*
|
|
669
|
+
* Maps places (by name) to integer token counts. Only stores places with count > 0.
|
|
670
|
+
* Used for invariant computation and structural verification, not runtime execution.
|
|
671
|
+
*/
|
|
672
|
+
declare class MarkingState {
|
|
673
|
+
private readonly tokenCounts;
|
|
674
|
+
private readonly placesByName;
|
|
675
|
+
/** @internal Use {@link MarkingState.builder} or {@link MarkingState.empty} to create instances. */
|
|
676
|
+
constructor(key: symbol, tokenCounts: Map<string, number>, placesByName: Map<string, Place<any>>);
|
|
677
|
+
/** Returns the token count for a place (0 if absent). */
|
|
678
|
+
tokens(place: Place<any>): number;
|
|
679
|
+
/** Checks if a place has at least one token. */
|
|
680
|
+
hasTokens(place: Place<any>): boolean;
|
|
681
|
+
/** Checks if any of the given places has tokens. */
|
|
682
|
+
hasTokensInAny(places: Iterable<Place<any>>): boolean;
|
|
683
|
+
/** Returns all places with tokens > 0. */
|
|
684
|
+
placesWithTokens(): Place<any>[];
|
|
685
|
+
/** Returns the total number of tokens. */
|
|
686
|
+
totalTokens(): number;
|
|
687
|
+
/** Checks if no tokens exist anywhere. */
|
|
688
|
+
isEmpty(): boolean;
|
|
689
|
+
toString(): string;
|
|
690
|
+
static empty(): MarkingState;
|
|
691
|
+
static builder(): MarkingStateBuilder;
|
|
692
|
+
}
|
|
693
|
+
declare class MarkingStateBuilder {
|
|
694
|
+
private readonly tokenCounts;
|
|
695
|
+
private readonly placesByName;
|
|
696
|
+
/** Sets the token count for a place. */
|
|
697
|
+
tokens(place: Place<any>, count: number): this;
|
|
698
|
+
/** Adds tokens to a place. */
|
|
699
|
+
addTokens(place: Place<any>, count: number): this;
|
|
700
|
+
/** Removes tokens from a place. Throws if insufficient. */
|
|
701
|
+
removeTokens(place: Place<any>, count: number): this;
|
|
702
|
+
/** Copies all token counts from another marking state. */
|
|
703
|
+
copyFrom(other: MarkingState): this;
|
|
704
|
+
build(): MarkingState;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* A P-invariant (place invariant) of a Petri net.
|
|
709
|
+
*
|
|
710
|
+
* A P-invariant is a vector y such that y^T * C = 0, where C is the
|
|
711
|
+
* incidence matrix. This means that for any reachable marking M:
|
|
712
|
+
* sum(y_i * M_i) = constant, where constant = sum(y_i * M0_i).
|
|
713
|
+
*
|
|
714
|
+
* P-invariants provide structural bounds on places and are used as
|
|
715
|
+
* strengthening lemmas for the IC3/PDR engine.
|
|
716
|
+
*/
|
|
717
|
+
interface PInvariant {
|
|
718
|
+
/** Weight vector (one entry per place index). */
|
|
719
|
+
readonly weights: readonly number[];
|
|
720
|
+
/** The invariant value sum(y_i * M0_i). */
|
|
721
|
+
readonly constant: number;
|
|
722
|
+
/** Set of place indices where weight != 0. */
|
|
723
|
+
readonly support: ReadonlySet<number>;
|
|
724
|
+
}
|
|
725
|
+
declare function pInvariant(weights: number[], constant: number, support: Set<number>): PInvariant;
|
|
726
|
+
declare function pInvariantToString(inv: PInvariant): string;
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Verification verdict.
|
|
730
|
+
*/
|
|
731
|
+
type Verdict = Proven | Violated | Unknown;
|
|
732
|
+
/** Property proven safe. No reachable state violates it. */
|
|
733
|
+
interface Proven {
|
|
734
|
+
readonly type: 'proven';
|
|
735
|
+
readonly method: string;
|
|
736
|
+
readonly inductiveInvariant: string | null;
|
|
737
|
+
}
|
|
738
|
+
/** Property violated. A counterexample trace is available. */
|
|
739
|
+
interface Violated {
|
|
740
|
+
readonly type: 'violated';
|
|
741
|
+
}
|
|
742
|
+
/** Could not determine. */
|
|
743
|
+
interface Unknown {
|
|
744
|
+
readonly type: 'unknown';
|
|
745
|
+
readonly reason: string;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Solver statistics.
|
|
749
|
+
*/
|
|
750
|
+
interface SmtStatistics {
|
|
751
|
+
readonly places: number;
|
|
752
|
+
readonly transitions: number;
|
|
753
|
+
readonly invariantsFound: number;
|
|
754
|
+
readonly structuralResult: string;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Result of SMT-based verification.
|
|
758
|
+
*/
|
|
759
|
+
interface SmtVerificationResult {
|
|
760
|
+
readonly verdict: Verdict;
|
|
761
|
+
readonly report: string;
|
|
762
|
+
readonly invariants: readonly PInvariant[];
|
|
763
|
+
readonly discoveredInvariants: readonly string[];
|
|
764
|
+
readonly counterexampleTrace: readonly MarkingState[];
|
|
765
|
+
readonly counterexampleTransitions: readonly string[];
|
|
766
|
+
readonly elapsedMs: number;
|
|
767
|
+
readonly statistics: SmtStatistics;
|
|
768
|
+
}
|
|
769
|
+
declare function isProven(result: SmtVerificationResult): boolean;
|
|
770
|
+
declare function isViolated(result: SmtVerificationResult): boolean;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Token-source supplier used by the verification harness to seed a synthetic
|
|
774
|
+
* environment place for a given input port, per
|
|
775
|
+
* `spec/11-modular-composition.md` requirement **MOD-051** AC #3.
|
|
776
|
+
*
|
|
777
|
+
* The supplier's presence is what bounds the input behavior in the synthetic
|
|
778
|
+
* harness; it is invoked once at synthetic-net construction time so that
|
|
779
|
+
* supplier-side errors surface eagerly, not at verification time. The
|
|
780
|
+
* concrete token value is not consumed by the verifier itself (which operates
|
|
781
|
+
* on the integer marking projection per [VER-004]); it merely materialises
|
|
782
|
+
* the seed token type and surfaces user errors.
|
|
783
|
+
*/
|
|
784
|
+
type TokenSupplier = () => Token<unknown>;
|
|
785
|
+
/**
|
|
786
|
+
* Harness driving local property verification of a subnet definition per
|
|
787
|
+
* `spec/11-modular-composition.md` requirement **MOD-051**.
|
|
788
|
+
*
|
|
789
|
+
* The harness is a value carrier consumed by
|
|
790
|
+
* {@link import('../core/subnet-def.js').SubnetDef.verify}. It supplies the
|
|
791
|
+
* three pieces of information needed to wrap a subnet in a synthetic
|
|
792
|
+
* enclosing net for verification:
|
|
793
|
+
*
|
|
794
|
+
* 1. A {@link params} value of the subnet's parameter type.
|
|
795
|
+
* 2. A {@link portInputGenerators} map from **input port name** (original /
|
|
796
|
+
* pre-prefix) to a {@link TokenSupplier}. The supplier's presence is what
|
|
797
|
+
* bounds the input behavior in the synthetic harness; the supplier is
|
|
798
|
+
* invoked at synthetic-net construction time when the harness wires up the
|
|
799
|
+
* {@link import('../core/place.js').EnvironmentPlace} associated with the
|
|
800
|
+
* port.
|
|
801
|
+
* 3. A set of {@link properties} to check (per [VER-002] /
|
|
802
|
+
* {@link SmtProperty}).
|
|
803
|
+
*
|
|
804
|
+
* Each entry in {@link portInputGenerators} MUST correspond to an input or
|
|
805
|
+
* in-out port declared on the subnet's interface. Output-only ports never
|
|
806
|
+
* appear in the generator map; instead, the harness wires them to a synthetic
|
|
807
|
+
* observation place visible to the verifier.
|
|
808
|
+
*
|
|
809
|
+
* The map and property collection accept either the canonical
|
|
810
|
+
* `Map`/`ReadonlySet` form or a plain `Record`/`readonly array` for ergonomic
|
|
811
|
+
* inline construction; `SubnetDef.verify` normalises both.
|
|
812
|
+
*
|
|
813
|
+
* @typeParam P parameter type carried through to the subnet under test
|
|
814
|
+
*/
|
|
815
|
+
interface VerificationHarness<P = void> {
|
|
816
|
+
/**
|
|
817
|
+
* Parameter value supplied to
|
|
818
|
+
* {@link import('../core/subnet-def.js').SubnetDef.instantiate} for the
|
|
819
|
+
* system-under-test instance. Use `undefined` (or `null`) for `P = void`.
|
|
820
|
+
*/
|
|
821
|
+
readonly params: P;
|
|
822
|
+
/**
|
|
823
|
+
* Map (or `Record`) from **input port name** to a {@link TokenSupplier} that
|
|
824
|
+
* seeds the synthetic environment place for that port. Output-only ports
|
|
825
|
+
* MUST NOT appear; in-out ports MUST appear (mirrors the Java harness).
|
|
826
|
+
*/
|
|
827
|
+
readonly portInputGenerators: ReadonlyMap<string, TokenSupplier> | Readonly<Record<string, TokenSupplier>>;
|
|
828
|
+
/**
|
|
829
|
+
* Safety properties to check on the synthetic enclosing net per
|
|
830
|
+
* {@link SmtProperty}. An empty collection is permitted but yields an empty
|
|
831
|
+
* {@link VerificationResult.perProperty}.
|
|
832
|
+
*/
|
|
833
|
+
readonly properties: ReadonlySet<SmtProperty> | readonly SmtProperty[];
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Aggregated outcome of a
|
|
837
|
+
* {@link import('../core/subnet-def.js').SubnetDef.verify} invocation per
|
|
838
|
+
* `spec/11-modular-composition.md` requirement **MOD-051**.
|
|
839
|
+
*
|
|
840
|
+
* The verifier is invoked once per {@link SmtProperty} declared in the
|
|
841
|
+
* harness; each invocation produces an {@link SmtVerificationResult}. This
|
|
842
|
+
* record carries the per-property results plus the synthetic enclosing net
|
|
843
|
+
* that was constructed for verification (useful for diagnostic output and
|
|
844
|
+
* tooling to render the harness wiring).
|
|
845
|
+
*/
|
|
846
|
+
interface VerificationResult {
|
|
847
|
+
/**
|
|
848
|
+
* The synthetic enclosing net assembled by `SubnetDef.verify(...)`: the
|
|
849
|
+
* renamed body of the subnet under test, with each input port bound to a
|
|
850
|
+
* synthetic environment place and each output port bound to a synthetic
|
|
851
|
+
* observation place.
|
|
852
|
+
*/
|
|
853
|
+
readonly syntheticNet: PetriNet;
|
|
854
|
+
/**
|
|
855
|
+
* Per-property verification results, in the iteration order of the
|
|
856
|
+
* harness's {@link VerificationHarness.properties} collection.
|
|
857
|
+
*/
|
|
858
|
+
readonly perProperty: ReadonlyMap<SmtProperty, SmtVerificationResult>;
|
|
859
|
+
/**
|
|
860
|
+
* Returns true when every property in the harness was proven safe.
|
|
861
|
+
* An empty harness (no properties) returns `true` vacuously.
|
|
862
|
+
*/
|
|
863
|
+
allProven(): boolean;
|
|
864
|
+
/**
|
|
865
|
+
* Returns true when at least one property was violated (counter-example
|
|
866
|
+
* found).
|
|
867
|
+
*/
|
|
868
|
+
anyViolated(): boolean;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* An open Petri net fragment paired with a declared {@link Interface}, per
|
|
873
|
+
* `spec/11-modular-composition.md` requirement **MOD-001**.
|
|
874
|
+
*
|
|
875
|
+
* A subnet definition is the reusable unit of composition. It carries:
|
|
876
|
+
* - A {@link name} (used as the originating-def label in `SubnetInstance` per [MOD-041]);
|
|
877
|
+
* - A {@link body} — a structurally complete `PetriNet` per [CORE-040];
|
|
878
|
+
* - An {@link iface} — the set of exposed ports and channels per [MOD-003] / [MOD-005].
|
|
879
|
+
*
|
|
880
|
+
* `SubnetDef` is the open variant of the {@link Subnet} discriminated union
|
|
881
|
+
* defined in `subnet.ts` per **MOD-002**.
|
|
882
|
+
*
|
|
883
|
+
* The {@link SubnetDef.fromNet} retrofit factory per **MOD-014** wraps an
|
|
884
|
+
* existing closed `PetriNet` plus an `Interface` into an unparameterised
|
|
885
|
+
* `SubnetDef<void>`, applying the same per-element validation as the
|
|
886
|
+
* builder's `build()`.
|
|
887
|
+
*
|
|
888
|
+
* @typeParam P parameter type carried through to `Instance.params`
|
|
889
|
+
* (use `void` for unparameterised subnets)
|
|
890
|
+
*/
|
|
891
|
+
declare class SubnetDef<P = void> {
|
|
892
|
+
readonly name: string;
|
|
893
|
+
readonly body: PetriNet;
|
|
894
|
+
readonly iface: Interface;
|
|
895
|
+
/** @internal Use {@link SubnetDef.builder} or {@link SubnetDef.fromNet} to create instances. */
|
|
896
|
+
constructor(key: symbol, name: string, body: PetriNet, iface: Interface);
|
|
897
|
+
/**
|
|
898
|
+
* Produces a renamed module instance per **MOD-010**, **MOD-011**, **MOD-012**,
|
|
899
|
+
* and **MOD-030**.
|
|
900
|
+
*
|
|
901
|
+
* The rename pass walks every place and transition of the body net,
|
|
902
|
+
* substituting each name with `prefix + "/" + originalName`, and rebuilds
|
|
903
|
+
* every arc with rewritten place references. Transition timing, priority,
|
|
904
|
+
* and action are carried through by reference (action sharing per
|
|
905
|
+
* [MOD-030]). The renamed body is itself a structurally valid `PetriNet`
|
|
906
|
+
* per [CORE-040]; per-instance state isolation per [MOD-012] is a
|
|
907
|
+
* structural consequence of distinct prefixed names.
|
|
908
|
+
*
|
|
909
|
+
* ## Prefix validation
|
|
910
|
+
*
|
|
911
|
+
* The `"/"` character is reserved as the prefix separator (per [MOD-010]).
|
|
912
|
+
* User-supplied prefixes MUST NOT contain `"/"`; nested instantiation is
|
|
913
|
+
* performed by the future `PetriNetBuilder.compose(...)` mechanism (per
|
|
914
|
+
* [MOD-013]). A prefix containing `"/"` raises an `Error`.
|
|
915
|
+
*
|
|
916
|
+
* @param prefix the rename prefix (non-empty, must not contain `"/"`)
|
|
917
|
+
* @param params the parameter value carried through to `Instance.params`
|
|
918
|
+
* (may be omitted when `P` is `void`)
|
|
919
|
+
* @throws when `prefix` is empty or contains `"/"`
|
|
920
|
+
*/
|
|
921
|
+
instantiate(prefix: string, params?: P): Instance<P>;
|
|
922
|
+
/**
|
|
923
|
+
* Verifies safety properties of this subnet definition **in isolation** per
|
|
924
|
+
* **MOD-051**, by wrapping it in a synthetic enclosing net where each input
|
|
925
|
+
* port is fed by an {@link EnvironmentPlace} (token-source per the harness
|
|
926
|
+
* generator) and each output port is observed via a synthetic place. The
|
|
927
|
+
* standard {@link SmtVerifier} (per [MOD-050]) is invoked once per property
|
|
928
|
+
* declared in the harness; the resulting per-property outcomes are
|
|
929
|
+
* aggregated into a {@link VerificationResult}.
|
|
930
|
+
*
|
|
931
|
+
* ## Synthetic-net construction
|
|
932
|
+
*
|
|
933
|
+
* The synthetic enclosing net is built by:
|
|
934
|
+
* 1. Instantiating this `SubnetDef` with the prefix `"sut"` (system-under-test)
|
|
935
|
+
* and `harness.params`.
|
|
936
|
+
* 2. For each input or in-out port on the interface, looking up the
|
|
937
|
+
* harness generator by port name, allocating a synthetic
|
|
938
|
+
* {@link Place}`<unknown>` of the same conceptual token type as the
|
|
939
|
+
* port, wrapping it in an {@link EnvironmentPlace}, and binding the
|
|
940
|
+
* port to that synthetic place via
|
|
941
|
+
* {@link import('./petri-net.js').PetriNetBuilder.compose}. The supplier
|
|
942
|
+
* is invoked once at construction time to materialize the seed token —
|
|
943
|
+
* its presence is what bounds the input behavior under analysis. **If
|
|
944
|
+
* the harness map is missing a generator for a required input or in-out
|
|
945
|
+
* port, an `Error` is thrown.**
|
|
946
|
+
* 3. For each output or in-out port, allocating a synthetic observation
|
|
947
|
+
* {@link Place}`<unknown>` and binding the port to it via the same
|
|
948
|
+
* `compose(...)` call. The verifier inspects this place's reachability
|
|
949
|
+
* / marking through the standard property APIs ({@link SmtProperty}).
|
|
950
|
+
* 4. Building the resulting flat {@link PetriNet} per [MOD-023] (the
|
|
951
|
+
* verifier is composition-unaware per [MOD-050]).
|
|
952
|
+
*
|
|
953
|
+
* ## Per-property invocation
|
|
954
|
+
*
|
|
955
|
+
* Each {@link SmtProperty} in the harness is verified independently against
|
|
956
|
+
* the same synthetic net. The synthetic environment places are passed
|
|
957
|
+
* through to the verifier so that places driven by the harness generators
|
|
958
|
+
* are treated under the verifier's environment-analysis semantics rather
|
|
959
|
+
* than as ordinary sink places.
|
|
960
|
+
*
|
|
961
|
+
* @param harness the verification harness — supplies parameters, input-port
|
|
962
|
+
* token generators, and the property set
|
|
963
|
+
* @returns a {@link VerificationResult} aggregating per-property
|
|
964
|
+
* {@link SmtVerificationResult}s
|
|
965
|
+
* @throws when an input or in-out port is missing a harness generator
|
|
966
|
+
*/
|
|
967
|
+
verify(harness: VerificationHarness<P>): Promise<VerificationResult>;
|
|
968
|
+
static builder<P = void>(name: string): SubnetDefBuilder<P>;
|
|
969
|
+
/**
|
|
970
|
+
* Retrofit utility per **MOD-014**: wraps an existing closed {@link PetriNet}
|
|
971
|
+
* plus an {@link Interface} into an unparameterised `SubnetDef<void>`.
|
|
972
|
+
*
|
|
973
|
+
* Validation per **MOD-014** / **MOD-006** is enforced before the result is
|
|
974
|
+
* constructed:
|
|
975
|
+
* - Every port's underlying `Place` must be present in `net.places`.
|
|
976
|
+
* - Every channel's underlying `Transition` must be present in `net.transitions`.
|
|
977
|
+
* - Port and channel name uniqueness is re-validated defensively (the
|
|
978
|
+
* `Interface` builder already enforces this; hand-built `Interface`
|
|
979
|
+
* values bypass that path).
|
|
980
|
+
*
|
|
981
|
+
* The resulting subnet definition is unparameterised (parameter type is `void`).
|
|
982
|
+
*
|
|
983
|
+
* @throws when a port place is not in `net.places`, a channel transition is
|
|
984
|
+
* not in `net.transitions`, or port/channel names are not unique.
|
|
985
|
+
*/
|
|
986
|
+
static fromNet(net: PetriNet, iface: Interface): SubnetDef<void>;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Fluent builder for {@link SubnetDef}.
|
|
990
|
+
*
|
|
991
|
+
* Validation per **MOD-006** is enforced at {@link build}:
|
|
992
|
+
* - Every port's underlying place must be in the body net.
|
|
993
|
+
* - Every channel's underlying transition must be in the body net.
|
|
994
|
+
* - Port names must be unique within the port namespace.
|
|
995
|
+
* - Channel names must be unique within the channel namespace.
|
|
996
|
+
*/
|
|
997
|
+
declare class SubnetDefBuilder<P = void> {
|
|
998
|
+
private readonly _name;
|
|
999
|
+
private readonly _bodyBuilder;
|
|
1000
|
+
private readonly _ports;
|
|
1001
|
+
private readonly _channels;
|
|
1002
|
+
constructor(name: string);
|
|
1003
|
+
transition(transition: Transition): this;
|
|
1004
|
+
transitions(...transitions: Transition[]): this;
|
|
1005
|
+
place<T>(place: Place<T>): this;
|
|
1006
|
+
inputPort<T>(name: string, place: Place<T>): this;
|
|
1007
|
+
outputPort<T>(name: string, place: Place<T>): this;
|
|
1008
|
+
inoutPort<T>(name: string, place: Place<T>): this;
|
|
1009
|
+
channel(name: string, transition: Transition): this;
|
|
1010
|
+
build(): SubnetDef<P>;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Lightweight, debug-UI-facing descriptor of one composed subnet instance,
|
|
1015
|
+
* per `spec/11-modular-composition.md` requirement **MOD-041**.
|
|
1016
|
+
*
|
|
1017
|
+
* Created by `Instance.descriptor()` after a subnet has been instantiated and
|
|
1018
|
+
* (eventually) composed into an enclosing net. The descriptor is consumed by
|
|
1019
|
+
* the debug protocol's `Subscribed` response so clients can render collapsed
|
|
1020
|
+
* subnet clusters, per-instance highlighting, and "show only this instance"
|
|
1021
|
+
* filters in the debug UI.
|
|
1022
|
+
*
|
|
1023
|
+
* This interface carries only structural metadata — it is observability-only
|
|
1024
|
+
* and does not affect runtime behaviour of the composed flat net.
|
|
1025
|
+
*/
|
|
1026
|
+
interface SubnetInstance {
|
|
1027
|
+
/** The instantiation prefix (e.g. `"buf1"` or `"outer/inner"` for nested instances). */
|
|
1028
|
+
readonly prefix: string;
|
|
1029
|
+
/** The originating subnet definition's name (null when not available). */
|
|
1030
|
+
readonly defName: string | null;
|
|
1031
|
+
/** Full prefixed transition names belonging to this instance. */
|
|
1032
|
+
readonly transitions: readonly string[];
|
|
1033
|
+
/** Full prefixed place names exposed as ports. */
|
|
1034
|
+
readonly exposedPlaces: readonly string[];
|
|
1035
|
+
/** The parameter value supplied at instantiation. */
|
|
1036
|
+
readonly params: unknown;
|
|
1037
|
+
/** The parent instance prefix when this is a nested instance, otherwise null. */
|
|
1038
|
+
readonly parentPrefix: string | null;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* A typed module instance produced by {@link SubnetDef.instantiate}, per
|
|
1043
|
+
* `spec/11-modular-composition.md` requirements **MOD-010** (creation),
|
|
1044
|
+
* **MOD-011** (typed handle map), **MOD-012** (per-instance state isolation),
|
|
1045
|
+
* and **MOD-030** (action binding).
|
|
1046
|
+
*
|
|
1047
|
+
* An instance carries:
|
|
1048
|
+
* - The {@link prefix} used to rename body elements (per [MOD-010] separator `"/"`);
|
|
1049
|
+
* - A reference to the originating {@link SubnetDef};
|
|
1050
|
+
* - The renamed body — a structurally valid `PetriNet` per [CORE-040];
|
|
1051
|
+
* - Typed lookup handles for ports and channels keyed by their **original**
|
|
1052
|
+
* (pre-prefix) names;
|
|
1053
|
+
* - The `params` value supplied at instantiation.
|
|
1054
|
+
*
|
|
1055
|
+
* **Scaffolding status**: this class ships in scaffolding form. The
|
|
1056
|
+
* {@link bindActions} method throws `Error("not implemented")` until the
|
|
1057
|
+
* `instantiate` rename pass lands in the next task. Direct accessors are
|
|
1058
|
+
* wired up so downstream code can take dependencies on the API shape today.
|
|
1059
|
+
*
|
|
1060
|
+
* @typeParam P parameter type (use `void` for unparameterised subnets)
|
|
1061
|
+
*/
|
|
1062
|
+
declare class Instance<P = void> {
|
|
1063
|
+
readonly prefix: string;
|
|
1064
|
+
readonly def: SubnetDef<P>;
|
|
1065
|
+
readonly renamedBody: PetriNet;
|
|
1066
|
+
readonly portHandles: ReadonlyMap<string, Place<unknown>>;
|
|
1067
|
+
readonly channelHandles: ReadonlyMap<string, Transition>;
|
|
1068
|
+
readonly params: P;
|
|
1069
|
+
/**
|
|
1070
|
+
* @internal Use {@link SubnetDef.instantiate} (or the internal factory
|
|
1071
|
+
* {@link __createInstance}) to create instances.
|
|
1072
|
+
*/
|
|
1073
|
+
constructor(key: symbol, prefix: string, def: SubnetDef<P>, renamedBody: PetriNet, portHandles: ReadonlyMap<string, Place<unknown>>, channelHandles: ReadonlyMap<string, Transition>, params: P);
|
|
1074
|
+
/**
|
|
1075
|
+
* Returns the renamed {@link Place} corresponding to the named port.
|
|
1076
|
+
*
|
|
1077
|
+
* The port name is the **original** (pre-prefix) name as declared in the
|
|
1078
|
+
* subnet's `Interface`. Per **MOD-022**, TypeScript enforces token-type
|
|
1079
|
+
* compatibility at compile time only; this method does not validate `T`
|
|
1080
|
+
* at runtime. A missing name raises an `Error`.
|
|
1081
|
+
*
|
|
1082
|
+
* @throws when the port name is unknown
|
|
1083
|
+
*/
|
|
1084
|
+
port<T>(name: string): Place<T>;
|
|
1085
|
+
/**
|
|
1086
|
+
* Returns the renamed {@link Transition} corresponding to the named channel.
|
|
1087
|
+
*
|
|
1088
|
+
* @throws when the channel name is unknown
|
|
1089
|
+
*/
|
|
1090
|
+
channel(name: string): Transition;
|
|
1091
|
+
/**
|
|
1092
|
+
* Returns the debug-UI descriptor for this instance per **MOD-041**.
|
|
1093
|
+
*/
|
|
1094
|
+
descriptor(): SubnetInstance;
|
|
1095
|
+
/**
|
|
1096
|
+
* Produces a derived instance whose specified transitions (named by their
|
|
1097
|
+
* **original**, pre-prefix names) carry the supplied actions per **MOD-030**.
|
|
1098
|
+
*
|
|
1099
|
+
* For each entry `[originalName, action]`, the renamed body is searched for
|
|
1100
|
+
* the transition whose name equals `prefix + "/" + originalName`. The
|
|
1101
|
+
* resulting instance shares the original `def`, `prefix`, `params`, and
|
|
1102
|
+
* port/channel handle topology — only the renamed body is rebuilt with new
|
|
1103
|
+
* actions. Per **MOD-030**, calling `bindActions` on one instance does NOT
|
|
1104
|
+
* affect the actions held by other instances of the same `def`.
|
|
1105
|
+
*
|
|
1106
|
+
* Unrecognised original names raise an `Error` so typos surface eagerly.
|
|
1107
|
+
*/
|
|
1108
|
+
bindActions(actionsByOriginalName: Record<string, TransitionAction>): Instance<P>;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Typed binding builder for {@link PetriNetBuilder.compose}, per
|
|
1113
|
+
* `spec/11-modular-composition.md` requirements **MOD-020** (composition
|
|
1114
|
+
* operation) and **MOD-022** (type compatibility).
|
|
1115
|
+
*
|
|
1116
|
+
* `ComposeBindings` is a write-only collector: callers register port and
|
|
1117
|
+
* channel bindings against an instance's interface, and the host builder
|
|
1118
|
+
* consumes the recorded mappings as it merges the instance into the
|
|
1119
|
+
* enclosing net.
|
|
1120
|
+
*
|
|
1121
|
+
* ## Port bindings (MOD-020, MOD-022)
|
|
1122
|
+
*
|
|
1123
|
+
* {@link bindPort} merges an interface port with a caller-side place. The
|
|
1124
|
+
* port name is the **original** (pre-prefix) name declared in the subnet's
|
|
1125
|
+
* `Interface`; the caller place takes the port's slot in the resulting net.
|
|
1126
|
+
* Token-type compatibility is enforced at compile time only in TypeScript
|
|
1127
|
+
* (per [MOD-022]) — the typed `bindPort<T>` signature is the safety
|
|
1128
|
+
* mechanism. TypeScript erases generics at runtime, so a misuse via `as`
|
|
1129
|
+
* casts is structurally undetectable.
|
|
1130
|
+
*
|
|
1131
|
+
* ## Channel bindings (MOD-021)
|
|
1132
|
+
*
|
|
1133
|
+
* {@link bindChannel} records a synchronous channel binding: at compose time
|
|
1134
|
+
* the named instance-side interface transition is **merged** with the
|
|
1135
|
+
* supplied caller-side {@link Transition} into one transition in the
|
|
1136
|
+
* resulting flat net. Channel composition is implemented in task #13; until
|
|
1137
|
+
* then `compose(...)` raises an `Error` when any channel binding is present.
|
|
1138
|
+
*
|
|
1139
|
+
* ## Identity
|
|
1140
|
+
*
|
|
1141
|
+
* Instances are produced by {@link PetriNetBuilder.compose} and supplied to
|
|
1142
|
+
* the caller's callback. The constructor is symbol-guarded to prevent
|
|
1143
|
+
* direct construction.
|
|
1144
|
+
*/
|
|
1145
|
+
declare class ComposeBindings {
|
|
1146
|
+
private readonly _portBindings;
|
|
1147
|
+
private readonly _channelBindings;
|
|
1148
|
+
/**
|
|
1149
|
+
* @internal Use {@link PetriNetBuilder.compose} — instances are produced
|
|
1150
|
+
* by the host builder and supplied to the caller's callback.
|
|
1151
|
+
*/
|
|
1152
|
+
constructor(key: symbol);
|
|
1153
|
+
/**
|
|
1154
|
+
* Binds the named interface port to the given caller place per **MOD-020**.
|
|
1155
|
+
*
|
|
1156
|
+
* The port name is the **original** (pre-prefix) name declared in the
|
|
1157
|
+
* subnet's `Interface`. The typed `<T>` parameter ensures the caller
|
|
1158
|
+
* place's token type matches the interface port's token type at compile
|
|
1159
|
+
* time per [MOD-022]; TypeScript does not validate the type at runtime
|
|
1160
|
+
* because generics are erased.
|
|
1161
|
+
*
|
|
1162
|
+
* @throws when `portName` is already bound on this builder
|
|
1163
|
+
*/
|
|
1164
|
+
bindPort<T>(portName: string, callerPlace: Place<T>): this;
|
|
1165
|
+
/**
|
|
1166
|
+
* Records a synchronous channel binding per **MOD-021**: at compose time,
|
|
1167
|
+
* the instance-side renamed channel transition is merged with
|
|
1168
|
+
* `callerTransition` into a single transition in the resulting flat net.
|
|
1169
|
+
*
|
|
1170
|
+
* **Status**: channel composition is implemented in task #13. Recording a
|
|
1171
|
+
* channel binding here is permitted, but `compose(...)` currently raises
|
|
1172
|
+
* an `Error` when any channel binding is present.
|
|
1173
|
+
*
|
|
1174
|
+
* @throws when `channelName` is already bound on this builder
|
|
1175
|
+
*/
|
|
1176
|
+
bindChannel(channelName: string, callerTransition: Transition): this;
|
|
1177
|
+
/** Returns an unmodifiable view of the recorded port bindings. */
|
|
1178
|
+
portBindings(): ReadonlyMap<string, Place<unknown>>;
|
|
1179
|
+
/** Returns an unmodifiable view of the recorded channel bindings. */
|
|
1180
|
+
channelBindings(): ReadonlyMap<string, Transition>;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* A declaration that N typed places are to be treated as a single canonical
|
|
1185
|
+
* place in the resulting flat {@link import('./petri-net.js').PetriNet}, per
|
|
1186
|
+
* `spec/11-modular-composition.md` requirements **MOD-060** (fusion set
|
|
1187
|
+
* declaration) and **MOD-061** (fusion resolution at build).
|
|
1188
|
+
*
|
|
1189
|
+
* Fusion is **orthogonal to subnet composition**: composition merges places
|
|
1190
|
+
* via port-binding (one instance port place ↔ one caller place per binding);
|
|
1191
|
+
* fusion merges N places at once via N-ary equivalence, applied **after** all
|
|
1192
|
+
* `compose(...)` calls have flattened subnet instances into the enclosing
|
|
1193
|
+
* {@link import('./petri-net.js').PetriNetBuilder}. Fusion is the mechanism
|
|
1194
|
+
* for modeling shared cross-instance state — e.g., a global rate limiter
|
|
1195
|
+
* shared by three instances of a leaky-bucket subnet — without expressing the
|
|
1196
|
+
* shared resource as an interface port on every subnet.
|
|
1197
|
+
*
|
|
1198
|
+
* ## Canonical member
|
|
1199
|
+
*
|
|
1200
|
+
* The **first declared member** is the canonical place; the others are
|
|
1201
|
+
* non-canonical and get substituted away at
|
|
1202
|
+
* {@link import('./petri-net.js').PetriNetBuilder.build} time. The canonical
|
|
1203
|
+
* member's name and identity survive into the resulting flat net;
|
|
1204
|
+
* non-canonical members do not appear in the built net's place set.
|
|
1205
|
+
*
|
|
1206
|
+
* ## Token-type homogeneity (MOD-060)
|
|
1207
|
+
*
|
|
1208
|
+
* All members of a fusion set MUST share the same token type. **In
|
|
1209
|
+
* TypeScript** this is enforced at **compile time only** (via the typed
|
|
1210
|
+
* `<T>` parameter on {@link FusionSetBuilder.member} and the typed varargs of
|
|
1211
|
+
* {@link FusionSet.of}); per the TS-specific MOD-022 clause, no runtime
|
|
1212
|
+
* `tokenType` introspection exists because Place carries only a phantom
|
|
1213
|
+
* generic. The Java implementation enforces the same invariant at runtime.
|
|
1214
|
+
*
|
|
1215
|
+
* ## Single-member sets
|
|
1216
|
+
*
|
|
1217
|
+
* A fusion set with a single member is degenerate but allowed: it is a no-op
|
|
1218
|
+
* at fusion-resolution time (the canonical member maps to itself, no
|
|
1219
|
+
* substitution occurs). This matches the structural semantics of an N-ary
|
|
1220
|
+
* equivalence with N=1.
|
|
1221
|
+
*
|
|
1222
|
+
* ## Identity
|
|
1223
|
+
*
|
|
1224
|
+
* `FusionSet` is immutable after construction. The {@link members} array is
|
|
1225
|
+
* frozen; iteration order is the declaration order, with the first element
|
|
1226
|
+
* guaranteed to be the canonical member.
|
|
1227
|
+
*
|
|
1228
|
+
* @see import('./petri-net.js').PetriNetBuilder.fuse
|
|
1229
|
+
*/
|
|
1230
|
+
declare class FusionSet {
|
|
1231
|
+
readonly name: string;
|
|
1232
|
+
readonly members: readonly Place<unknown>[];
|
|
1233
|
+
/** @internal Use {@link FusionSet.builder} or {@link FusionSet.of} to create instances. */
|
|
1234
|
+
constructor(key: symbol, name: string, members: readonly Place<unknown>[]);
|
|
1235
|
+
/**
|
|
1236
|
+
* Returns the canonical member — by convention, the first declared member.
|
|
1237
|
+
* The canonical place's identity survives into the resulting flat net.
|
|
1238
|
+
*/
|
|
1239
|
+
get canonical(): Place<unknown>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Returns all members **except** the canonical member, in declaration order.
|
|
1242
|
+
* These are the places that get substituted away at
|
|
1243
|
+
* {@link import('./petri-net.js').PetriNetBuilder.build} time.
|
|
1244
|
+
*
|
|
1245
|
+
* For a single-member (degenerate) set, returns an empty array.
|
|
1246
|
+
*/
|
|
1247
|
+
nonCanonical(): readonly Place<unknown>[];
|
|
1248
|
+
toString(): string;
|
|
1249
|
+
/** Returns a fresh {@link FusionSetBuilder} with the given human-readable name. */
|
|
1250
|
+
static builder(name: string): FusionSetBuilder;
|
|
1251
|
+
/**
|
|
1252
|
+
* Convenience factory: builds a fusion set whose first member is `first`
|
|
1253
|
+
* (the canonical) and whose remaining members are `rest`, all sharing the
|
|
1254
|
+
* type `<T>`.
|
|
1255
|
+
*
|
|
1256
|
+
* The varargs form ensures static-type homogeneity at the call site (the
|
|
1257
|
+
* TypeScript compiler checks every `rest` entry is a `Place<T>`).
|
|
1258
|
+
*/
|
|
1259
|
+
static of<T>(name: string, first: Place<T>, ...rest: Place<T>[]): FusionSet;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Fluent builder for {@link FusionSet}.
|
|
1263
|
+
*
|
|
1264
|
+
* Members are appended in call order; the first member becomes the canonical
|
|
1265
|
+
* place. The typed `<T>` parameter on {@link member} is for compile-time
|
|
1266
|
+
* guidance only — TypeScript erases generics at runtime, so no homogeneity
|
|
1267
|
+
* check happens here.
|
|
1268
|
+
*/
|
|
1269
|
+
declare class FusionSetBuilder {
|
|
1270
|
+
private readonly _name;
|
|
1271
|
+
private readonly _members;
|
|
1272
|
+
constructor(name: string);
|
|
1273
|
+
/**
|
|
1274
|
+
* Appends a member to the fusion set. The first member becomes the canonical
|
|
1275
|
+
* place per the convention documented on {@link FusionSet}.
|
|
1276
|
+
*
|
|
1277
|
+
* The `<T>` parameter is for compile-time guidance only: callers writing
|
|
1278
|
+
* typed code at the same call site benefit from the compiler checking
|
|
1279
|
+
* `Place<T>` at each member.
|
|
1280
|
+
*/
|
|
1281
|
+
member<T>(place: Place<T>): this;
|
|
1282
|
+
/**
|
|
1283
|
+
* Builds the immutable {@link FusionSet}. Validates that the set has at
|
|
1284
|
+
* least one member; the empty set is rejected as malformed per **MOD-060**.
|
|
1285
|
+
* Single-member sets are accepted as a structurally degenerate no-op.
|
|
1286
|
+
*/
|
|
1287
|
+
build(): FusionSet;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Immutable definition of a Time Petri Net structure.
|
|
1292
|
+
*
|
|
1293
|
+
* A PetriNet is a reusable definition that can be executed multiple times
|
|
1294
|
+
* with different initial markings. Places are auto-collected from transitions.
|
|
1295
|
+
*/
|
|
1296
|
+
declare class PetriNet {
|
|
1297
|
+
readonly name: string;
|
|
1298
|
+
readonly places: ReadonlySet<Place<any>>;
|
|
1299
|
+
readonly transitions: ReadonlySet<Transition>;
|
|
1300
|
+
/** @internal Use {@link PetriNet.builder} to create instances. */
|
|
1301
|
+
constructor(key: symbol, name: string, places: ReadonlySet<Place<any>>, transitions: ReadonlySet<Transition>);
|
|
1302
|
+
/**
|
|
1303
|
+
* Creates a new PetriNet with actions bound to transitions by name.
|
|
1304
|
+
* Unbound transitions keep passthrough action.
|
|
1305
|
+
*/
|
|
1306
|
+
bindActions(actionBindings: Map<string, TransitionAction> | Record<string, TransitionAction>): PetriNet;
|
|
1307
|
+
/**
|
|
1308
|
+
* Creates a new PetriNet with actions bound via a resolver function.
|
|
1309
|
+
*/
|
|
1310
|
+
bindActionsWithResolver(actionResolver: (name: string) => TransitionAction): PetriNet;
|
|
1311
|
+
static builder(name: string): PetriNetBuilder;
|
|
1312
|
+
}
|
|
1313
|
+
declare class PetriNetBuilder {
|
|
1314
|
+
private readonly _name;
|
|
1315
|
+
private readonly _places;
|
|
1316
|
+
private readonly _transitions;
|
|
1317
|
+
private readonly _fusionSets;
|
|
1318
|
+
constructor(name: string);
|
|
1319
|
+
/** Add an explicit place. */
|
|
1320
|
+
place(place: Place<any>): this;
|
|
1321
|
+
/** Add explicit places. */
|
|
1322
|
+
places(...places: Place<any>[]): this;
|
|
1323
|
+
/** Add a transition (auto-collects places from arcs). */
|
|
1324
|
+
transition(transition: Transition): this;
|
|
1325
|
+
/** Add transitions (auto-collects places from arcs). */
|
|
1326
|
+
transitions(...transitions: Transition[]): this;
|
|
1327
|
+
/**
|
|
1328
|
+
* Composes a subnet {@link Instance} into this builder per **MOD-020**
|
|
1329
|
+
* (composition operation), **MOD-021** (channel composition), **MOD-022**
|
|
1330
|
+
* (type compatibility), and **MOD-023** (composition produces a flat net).
|
|
1331
|
+
*
|
|
1332
|
+
* Two overloads are supported:
|
|
1333
|
+
*
|
|
1334
|
+
* 1. **Map / Record overload** — the runtime-checked form. Pass a
|
|
1335
|
+
* `Map<string, Place<unknown>>` or a `Record<string, Place<unknown>>`
|
|
1336
|
+
* keyed by the subnet's **original** (pre-prefix) port names. No
|
|
1337
|
+
* channel bindings are recorded by this overload.
|
|
1338
|
+
*
|
|
1339
|
+
* 2. **Callback overload** — the typed form. Pass a callback receiving a
|
|
1340
|
+
* fresh {@link ComposeBindings}; register port bindings via
|
|
1341
|
+
* `bindings.bindPort<T>(name, place)` so TypeScript checks each
|
|
1342
|
+
* binding's token type at compile time per [MOD-022]. Synchronous
|
|
1343
|
+
* channels may be merged with caller-side transitions via
|
|
1344
|
+
* `bindings.bindChannel(name, t)` per [MOD-021].
|
|
1345
|
+
*
|
|
1346
|
+
* For each port binding `(portName -> callerPlace)`, the instance's
|
|
1347
|
+
* renamed port place is substituted with the caller place at every arc
|
|
1348
|
+
* in the instance's renamed body via the `subnet-rewriter` module.
|
|
1349
|
+
* Internal (non-port) places of the instance flow through with their
|
|
1350
|
+
* prefixed names. Composition is **eager** — the rewrite happens here,
|
|
1351
|
+
* not at `build()` (per [MOD-020] AC #5).
|
|
1352
|
+
*
|
|
1353
|
+
* For each channel binding `(channelName -> callerTransition)`, the
|
|
1354
|
+
* instance's renamed channel transition and the caller-side transition
|
|
1355
|
+
* are merged into one transition in the resulting flat net per [MOD-021].
|
|
1356
|
+
* If the caller-side transition has not been added to this builder yet,
|
|
1357
|
+
* it is added implicitly during the merge step.
|
|
1358
|
+
*
|
|
1359
|
+
* @throws when a port name is unknown on the instance's interface, when a
|
|
1360
|
+
* channel name is unknown on the interface, or when caller- and
|
|
1361
|
+
* instance-side transition timings conflict (per [MOD-021]).
|
|
1362
|
+
*/
|
|
1363
|
+
compose(instance: Instance<unknown>, portMappings: ReadonlyMap<string, Place<unknown>> | Record<string, Place<unknown>>): this;
|
|
1364
|
+
compose(instance: Instance<unknown>, bind: (b: ComposeBindings) => void): this;
|
|
1365
|
+
/**
|
|
1366
|
+
* @internal Shared compose implementation: validates port and channel
|
|
1367
|
+
* bindings, builds the place-substitution map, walks every renamed-body
|
|
1368
|
+
* transition through {@link substitutePlaces}, applies channel merges per
|
|
1369
|
+
* [MOD-021], and adds the resulting transitions to this builder.
|
|
1370
|
+
*
|
|
1371
|
+
* ## Channel-merge flow ([MOD-021])
|
|
1372
|
+
*
|
|
1373
|
+
* 1. Collect rewritten instance transitions into a working `Map<string,
|
|
1374
|
+
* Transition>` keyed by prefixed transition name (deferred — not yet
|
|
1375
|
+
* added to the builder's transition set).
|
|
1376
|
+
* 2. For each channel binding, resolve the renamed instance-side
|
|
1377
|
+
* transition through `instance.channel(channelName)`, then look up its
|
|
1378
|
+
* rewritten counterpart in the working map by name.
|
|
1379
|
+
* 3. Replace the working-map entry with a {@link mergeTransitions} result
|
|
1380
|
+
* that fuses caller-side + instance-side; remove the rewritten
|
|
1381
|
+
* instance-side entry. Also replace (or add) the caller-side
|
|
1382
|
+
* transition in this builder's transition set with the same merged
|
|
1383
|
+
* result, indexed under the caller's name slot.
|
|
1384
|
+
* 4. Add the surviving (un-merged) entries to this builder.
|
|
1385
|
+
*
|
|
1386
|
+
* The deferral matters: writing the rewritten instance transitions to the
|
|
1387
|
+
* builder eagerly would force a second "remove-then-replace" pass to
|
|
1388
|
+
* apply the channel merges, complicating the place-collection invariants.
|
|
1389
|
+
* Collecting first and merging second keeps the builder's transition set
|
|
1390
|
+
* finalized exactly once.
|
|
1391
|
+
*
|
|
1392
|
+
* Keying the working map by prefixed transition name (rather than by
|
|
1393
|
+
* Transition reference) is also robust against a prior
|
|
1394
|
+
* {@link Instance.bindActions} call that may have rebuilt the renamed-body
|
|
1395
|
+
* transitions, breaking identity equality between the body and the
|
|
1396
|
+
* channel-handle map — but the prefixed names remain stable.
|
|
1397
|
+
*/
|
|
1398
|
+
private composeInternal;
|
|
1399
|
+
/**
|
|
1400
|
+
* Registers one or more {@link FusionSet} declarations on this builder, per
|
|
1401
|
+
* **MOD-060** (fusion set declaration) and **MOD-061** (fusion resolution
|
|
1402
|
+
* at build).
|
|
1403
|
+
*
|
|
1404
|
+
* Fusion is **orthogonal to subnet composition**: fuse sets are accumulated
|
|
1405
|
+
* here and applied during {@link build} **after** all `compose(...)` calls
|
|
1406
|
+
* have flattened subnet instances into the builder's transition set.
|
|
1407
|
+
* Registration order is irrelevant to semantics — `fuse(set)` BEFORE
|
|
1408
|
+
* `compose(...)` and `fuse(set)` AFTER `compose(...)` both apply at the
|
|
1409
|
+
* same point in the build pipeline.
|
|
1410
|
+
*
|
|
1411
|
+
* ## Validation
|
|
1412
|
+
*
|
|
1413
|
+
* Cross-set overlap (a place appearing in two fusion sets) is detected at
|
|
1414
|
+
* {@link build} and reported as an `Error` naming the offending place and
|
|
1415
|
+
* both sets.
|
|
1416
|
+
*
|
|
1417
|
+
* Two overloads are supported:
|
|
1418
|
+
*
|
|
1419
|
+
* 1. **Spread overload** — pass one or more pre-built {@link FusionSet}
|
|
1420
|
+
* values (e.g., from `FusionSet.of(...)` or
|
|
1421
|
+
* `FusionSet.builder(...).build()`).
|
|
1422
|
+
* 2. **Sugar overload** — pass a callback receiving a fresh
|
|
1423
|
+
* {@link FusionSetBuilder} named after the enclosing net; register
|
|
1424
|
+
* members via `b.member(place)`. Equivalent to
|
|
1425
|
+
* `FusionSet.builder('<netName>-fusion').<callback>.build()` followed by
|
|
1426
|
+
* `fuse(...)` on the result.
|
|
1427
|
+
*/
|
|
1428
|
+
fuse(...sets: FusionSet[]): this;
|
|
1429
|
+
fuse(declarer: (b: FusionSetBuilder) => void): this;
|
|
1430
|
+
/**
|
|
1431
|
+
* Builds the immutable {@link PetriNet}, applying fusion resolution (per
|
|
1432
|
+
* **MOD-061**) AFTER all transition/composition accumulation:
|
|
1433
|
+
*
|
|
1434
|
+
* 1. Detect overlapping fusion sets — a single place declared in two sets
|
|
1435
|
+
* is rejected with an `Error`.
|
|
1436
|
+
* 2. Build the `non-canonical → canonical` substitution map across all
|
|
1437
|
+
* sets, keyed by non-canonical place name (matching the rewriter's
|
|
1438
|
+
* Map<string, Place<unknown>> convention — TypeScript Place identity is
|
|
1439
|
+
* name-based per `runtime/compiled-net.ts`).
|
|
1440
|
+
* 3. Walk every transition through {@link applyFusion} to rewrite arc place
|
|
1441
|
+
* references.
|
|
1442
|
+
* 4. Re-derive the place set from the rewritten transitions plus any
|
|
1443
|
+
* caller-declared standalone places, dropping non-canonical members.
|
|
1444
|
+
* Caller-declared standalone places that happen to be non-canonical
|
|
1445
|
+
* members are also dropped.
|
|
1446
|
+
*
|
|
1447
|
+
* If no fusion sets were registered, the build is the trivial
|
|
1448
|
+
* `new PetriNet(...)` — the fusion machinery has no per-build cost when
|
|
1449
|
+
* unused.
|
|
1450
|
+
*
|
|
1451
|
+
* @throws when two fusion sets share a place
|
|
1452
|
+
*/
|
|
1453
|
+
build(): PetriNet;
|
|
1454
|
+
/**
|
|
1455
|
+
* @internal Fusion-resolution pass per **MOD-061**. Split out from
|
|
1456
|
+
* {@link build} so the no-fusion fast path stays trivial.
|
|
1457
|
+
*/
|
|
1458
|
+
private buildWithFusion;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
export { and as $, type Arc as A, SubnetDefBuilder as B, type Channel as C, type SubnetInstance as D, type EnvironmentPlace as E, FusionSet as F, type Timing as G, type TimingDeadline as H, type In as I, type TimingDelayed as J, type TimingExact as K, type LogFn as L, MAX_DURATION_MS as M, type TimingImmediate as N, type Out as O, PetriNet as P, type TimingWindow as Q, TokenInput as R, SubnetDef as S, type Token as T, TokenOutput as U, type TransitionAction as V, TransitionBuilder as W, type VerificationHarness as X, type VerificationResult as Y, all as Z, allPlaces as _, type Place as a, placeBound as a$, andPlaces as a0, arcPlace as a1, atLeast as a2, consumptionCount as a3, deadline as a4, delayed as a5, earliest as a6, enumerateBranches as a7, environmentPlace as a8, exact as a9, transformFrom as aA, unitToken as aB, window as aC, withTimeout as aD, xor as aE, xorPlaces as aF, MarkingState as aG, type PInvariant as aH, MarkingStateBuilder as aI, type SmtProperty as aJ, type SmtVerificationResult as aK, type DeadlockFree as aL, type MutualExclusion as aM, type PlaceBound as aN, type Proven as aO, type SmtStatistics as aP, type TokenSupplier as aQ, type Unknown as aR, type Unreachable as aS, type Verdict as aT, type Violated as aU, deadlockFree as aV, isProven as aW, isViolated as aX, mutualExclusion as aY, pInvariant as aZ, pInvariantToString as a_, exactly as aa, fork as ab, forwardInput as ac, hasDeadline as ad, hasGuard as ae, immediate as af, inhibitorArc as ag, inputArc as ah, isUnit as ai, latest as aj, matchesGuard as ak, one as al, outPlace as am, outputArc as an, passthrough as ao, place as ap, produce as aq, readArc as ar, requiredCount as as, resetArc as at, timeout as au, timeoutPlace as av, tokenAt as aw, tokenOf as ax, transform as ay, transformAsync as az, Transition as b, propertyDescription as b0, unreachable as b1, TransitionContext as c, type ArcInhibitor as d, type ArcInput as e, type ArcOutput as f, type ArcRead as g, type ArcReset as h, ComposeBindings as i, FusionSetBuilder as j, type InAll as k, type InAtLeast as l, type InExactly as m, type InOne as n, Instance as o, Interface as p, InterfaceBuilder as q, type OutAnd as r, type OutForwardInput as s, type OutPlace as t, type OutTimeout as u, type OutXor as v, type OutputEntry as w, PetriNetBuilder as x, type Port as y, type PortDirection as z };
|