libpetri 5.0.0 → 6.0.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.
@@ -1,5 +1,5 @@
1
- import { b as Transition, a as Place, P as PetriNet, E as EnvironmentPlace, aO as EnvironmentAnalysisMode, aP as PInvariant, aQ as MarkingState, aR as SmtProperty, aS as MarkingStateBuilder, aT as SmtVerificationResult } from '../petri-net-CH7UvjOW.js';
2
- export { aU as BranchPlaceBound, aV as DeadlockFree, aW as JoinedOrDeadLettered, aX as MutualExclusion, aY as PlaceBound, aZ as Proven, a_ as SmtStatistics, a$ as TerminatesAtSink, b0 as TokenSupplier, b1 as Unknown, b2 as Unreachable, b3 as Verdict, $ as VerificationHarness, a0 as VerificationResult, b4 as Violated, b5 as alwaysAvailable, b5 as analysisAlwaysAvailable, b6 as analysisBounded, b7 as analysisIgnore, b6 as bounded, b8 as branchPlaceBound, b9 as deadlockFree, b7 as ignore, ba as isProven, bb as isViolated, bc as joinedOrDeadLettered, bd as mutualExclusion, be as pInvariant, bf as pInvariantToString, bg as placeBound, bh as propertyDescription, bi as terminatesAtSink, bj as unreachable } from '../petri-net-CH7UvjOW.js';
1
+ import { b as Transition, a as Place, P as PetriNet, E as EnvironmentPlace, aO as EnvironmentAnalysisMode, aP as PInvariant, aQ as MarkingState, aR as SmtProperty, aS as Verdict, aT as MarkingStateBuilder, aU as SmtVerificationResult } from '../petri-net-34SkD5RT.js';
2
+ export { aV as BranchPlaceBound, aW as DeadlockFree, aX as JoinedOrDeadLettered, aY as MutualExclusion, aZ as PlaceBound, a_ as Proven, a$ as QuiescentCount, b0 as SmtStatistics, b1 as TerminatesAtSink, b2 as TokenSupplier, b3 as Unknown, b4 as Unreachable, $ as VerificationHarness, a0 as VerificationResult, b5 as VerificationRoute, b6 as Violated, b7 as alwaysAvailable, b7 as analysisAlwaysAvailable, b8 as analysisBounded, b9 as analysisIgnore, b8 as bounded, ba as branchPlaceBound, bb as deadlockFree, b9 as ignore, bc as isProven, bd as isViolated, be as joinedOrDeadLettered, bf as mutualExclusion, bg as pInvariant, bh as pInvariantToString, bi as placeBound, bj as propertyDescription, bk as quiescentCount, bl as terminatesAtSink, bm as unreachable } from '../petri-net-34SkD5RT.js';
3
3
 
4
4
  /**
5
5
  * A flattened transition with pre/post vectors for SMT encoding.
@@ -115,7 +115,8 @@ declare class IncidenceMatrix {
115
115
  * - `consumeAll[p]`: true for `all`/`at-least` inputs (consume everything)
116
116
  * - Index arrays for inhibitor, read, and reset arcs
117
117
  *
118
- * Places are sorted by name for stable, deterministic indexing across runs.
118
+ * Places are sorted by name, in Unicode code-point order, for stable indexing across
119
+ * runs, hosts and implementations.
119
120
  */
120
121
 
121
122
  /**
@@ -253,6 +254,187 @@ declare function findMinimalSiphons(flatNet: FlatNet): ReadonlySet<number>[];
253
254
  */
254
255
  declare function findMaximalTrapIn(flatNet: FlatNet, places: ReadonlySet<number>): ReadonlySet<number>;
255
256
 
257
+ /**
258
+ * @module rest-set
259
+ *
260
+ * Where a token may come to rest without being stranded (VER-002, VER-014).
261
+ *
262
+ * `DeadlockFree` is violated by a quiescent marking that holds a token outside the
263
+ * places where resting is permitted. The permitted set has two layers:
264
+ *
265
+ * - the **declared sinks** (`SmtVerifier.sinkPlaces`), where a token may always rest;
266
+ * - the **conditional sinks** (`SmtVerifier.sinkPlacesWhen(marker, …)`), where a
267
+ * token may rest only while `marker` holds a token. A marked marker is a
268
+ * *designed terminal* — a halted or paused run — and the marker itself is at rest
269
+ * whenever it is marked.
270
+ *
271
+ * Declarations union: a token in `p` is excused when `p` is a declared sink, when
272
+ * `p` is a marker, or when some conditional set naming `p` has its marker marked.
273
+ * Every route that decides `DeadlockFree` — the flat and name-coloured CHC encoders,
274
+ * the abstract counterexample replay and the Route B name-partition graph — reads
275
+ * this one module, so the predicate cannot drift between them (VER-002 AC7).
276
+ *
277
+ * `TerminatesAtSink` is untouched by conditional declarations: it asks whether a
278
+ * declared sink was reached and reads only the unconditional set.
279
+ */
280
+
281
+ /** Places where a token may rest while `marker` holds a token. */
282
+ interface ConditionalSinks {
283
+ readonly marker: Place<any>;
284
+ readonly places: ReadonlySet<Place<any>>;
285
+ }
286
+ /**
287
+ * Per flat place, how a token resting there is excused: `null` when it never counts
288
+ * as stranded (a declared sink, or a marker), otherwise the ascending flat indices of
289
+ * the markers whose presence excuses it — empty when nothing does, so a token there
290
+ * is stranded whenever the marking is quiescent.
291
+ *
292
+ * Places and markers that do not resolve in the flat net contribute nothing, as an
293
+ * unresolved sink does: a mistyped marker makes the property stricter, never laxer.
294
+ */
295
+ declare function strandingExcuses(flatNet: FlatNet, sinkPlaces: ReadonlySet<Place<any>>, conditional: readonly ConditionalSinks[]): (readonly number[] | null)[];
296
+ /**
297
+ * Whether `m` holds a token that is stranded — outside every place where resting is
298
+ * permitted in `m`. The graph-route form of {@link strandingExcuses}.
299
+ */
300
+ declare function strandsToken(m: MarkingState, sinkPlaces: ReadonlySet<Place<any>>, conditional: readonly ConditionalSinks[]): boolean;
301
+ /**
302
+ * The places of `m` holding a stranded token, in `m`'s own order: empty exactly when
303
+ * {@link strandsToken} is false. [VER-022] names them in its violations.
304
+ */
305
+ declare function strandedPlaces(m: MarkingState, sinkPlaces: ReadonlySet<Place<any>>, conditional: readonly ConditionalSinks[]): Place<any>[];
306
+ /**
307
+ * The declarations as the report prints them after the property description:
308
+ * `sinks: a, b; when h: c, d; when p`, or `null` when nothing is declared.
309
+ * Declaration order throughout, so the four implementations render the same text.
310
+ */
311
+ declare function describeSinks(sinkPlaces: ReadonlySet<Place<any>>, conditional: readonly ConditionalSinks[]): string | null;
312
+
313
+ /**
314
+ * @module programming-error
315
+ *
316
+ * Telling a verification failure apart from a bug.
317
+ *
318
+ * The pipeline is full of `catch` blocks that turn a failure into `Unknown`, or
319
+ * into a weaker but still well-formed result. Every one of them was written for a
320
+ * real condition — the solver died, the transport timed out, the replay search ran
321
+ * out of budget — and every one of them quietly acquires a second meaning: *any*
322
+ * defect in the code it guards. Once both arrive as the same verdict they cannot
323
+ * be told apart, and a bug becomes a permanently plausible weaker answer instead
324
+ * of a loud failure. That is the most expensive failure shape this verifier has:
325
+ * a report that looks right and proves less.
326
+ *
327
+ * So the taxonomy is explicit. A `TypeError` or `ReferenceError` is never a
328
+ * verdict — it is a defect in libpetri or in a caller's net, and it propagates. A
329
+ * `RangeError` *is* a verdict: a stack overflow on a deep net is the capacity
330
+ * limit `Unknown` exists to report. Everything else — a dead solver, a bad reply,
331
+ * an exhausted budget — is the condition the catch was written for and passes
332
+ * through untouched.
333
+ */
334
+ /**
335
+ * Re-throws `e` when it is a programming defect rather than a verification
336
+ * outcome. Call it first in any `catch` that degrades a result.
337
+ */
338
+ declare function rethrowIfProgrammingError(e: unknown): void;
339
+
340
+ /**
341
+ * @module graph-decision
342
+ *
343
+ * The property predicate both state-class-graph routes decide, in one place.
344
+ *
345
+ * Two routes enumerate a finite graph of classes and read a verdict off it: the
346
+ * ν name-partition quotient of [VER-012] (`nu-scg-verifier`) and the plain
347
+ * bounded enumeration of [VER-017] (`scg-verifier`). They explore different
348
+ * graphs, but the question they ask of a class is identical, and [VER-002] AC7
349
+ * requires every route to decide the *same* predicate. Stating it once is what
350
+ * keeps that true: when the sink clause last lived in two copies, one of them
351
+ * drifted (NU-040 AC4).
352
+ */
353
+
354
+ /** A finite graph of classes, indexed `0 .. count - 1`, class 0 the initial one. */
355
+ interface ClassView {
356
+ readonly count: number;
357
+ /** The marking of class `i`. */
358
+ markingOf(i: number): MarkingState;
359
+ /** Whether class `i` has no successor — the graph's quiescence. */
360
+ isQuiescent(i: number): boolean;
361
+ }
362
+ /**
363
+ * The index of the first class witnessing a violation, or `-1` when the property
364
+ * holds across the whole graph.
365
+ *
366
+ * Quiescence-based properties read `isQuiescent`; reachability-safety properties
367
+ * read the marking alone. `DeadlockFree` uses the shared rest set of [VER-014],
368
+ * so a conditional sink excuses a token exactly as it does in the encoders.
369
+ */
370
+ declare function decideOverClasses(view: ClassView, property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, conditionalSinks?: readonly ConditionalSinks[]): number;
371
+
372
+ /**
373
+ * @module scg-verifier
374
+ *
375
+ * Bounded state-space enumeration ([VER-017]): decide a property by building the
376
+ * state-class graph and reading the verdict off it, when the graph closes within
377
+ * a class budget.
378
+ *
379
+ * IC3/PDR is built for state spaces that are wide and shallow. A workflow net is
380
+ * the opposite — narrow and deep: a forty-node pipeline has under two thousand
381
+ * reachable classes, but its diameter is the length of the pipeline, so the
382
+ * fixpoint engine needs a frame per stage and its cost climbs with the cube of
383
+ * the length. Enumerating the same net is linear in the state space and finishes
384
+ * in milliseconds. Measured on a forty-node chain (370 places): 410 s on the
385
+ * fixpoint path, 0.11 s here.
386
+ *
387
+ * The route is exact when the graph closes — sound *and* complete, so a
388
+ * `violated` is a real firing sequence rather than a possibly-spurious
389
+ * over-approximation, and a `proven` is never the `unknown` a fixpoint search
390
+ * runs out of time for.
391
+ *
392
+ * It applies only to an **untimed** net — every transition `immediate` — and that
393
+ * restriction is what makes the verdict interchangeable with the encoders'. The
394
+ * state-class graph carries firing domains, so on a timed net it would explore
395
+ * only the runs the timing admits and its `proven` would be the weaker timed
396
+ * claim; [VER-004] is explicit that the untimed proof is the stronger one, and a
397
+ * route must not quietly hand back a weaker claim than the one it replaced. On an
398
+ * untimed net no domain excludes anything, the graph explores exactly the untimed
399
+ * reachable set, and the two routes decide the same predicate over the same
400
+ * abstraction — enumeration simply decides it where the search may not.
401
+ *
402
+ * When the graph does not close within the budget the route declines and the
403
+ * caller runs the SMT pipeline unchanged: enumeration never turns a verdict into
404
+ * `unknown` that the solver could have decided.
405
+ */
406
+
407
+ /**
408
+ * Whether every transition is `immediate`, so the state-class graph explores the
409
+ * untimed reachable set exactly and its verdict is the encoders' claim rather
410
+ * than the weaker timed one. See this module's header.
411
+ */
412
+ declare function isUntimed(net: PetriNet): boolean;
413
+ /** The note a decided verdict carries into the report. */
414
+ declare const NOTE_ENUMERATED: string;
415
+ /** Outcome of the enumeration route. */
416
+ type ScgOutcome =
417
+ /** The graph closed and decided the property. */
418
+ {
419
+ readonly kind: 'decided';
420
+ readonly verdict: Verdict;
421
+ readonly trace: MarkingState[];
422
+ readonly transitions: string[];
423
+ readonly classCount: number;
424
+ }
425
+ /** The graph hit the class budget; the caller falls through to the SMT pipeline. */
426
+ | {
427
+ readonly kind: 'truncated';
428
+ readonly classCount: number;
429
+ };
430
+ /**
431
+ * Decides `property` by enumeration, or reports truncation.
432
+ *
433
+ * @param maxClasses the class budget; `<= 0` disables the route (the caller then
434
+ * never calls this).
435
+ */
436
+ declare function verifyViaStateClassGraph(net: PetriNet, initial: MarkingState, property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, maxClasses: number, conditionalSinks?: readonly ConditionalSinks[]): ScgOutcome;
437
+
256
438
  /** Environment variable naming the z3 executable (default: `z3` on `PATH`). */
257
439
  declare const Z3_ENV = "LIBPETRI_Z3";
258
440
  /** Environment variable naming a directory that receives every script and reply. */
@@ -406,14 +588,15 @@ type CertificateCheckOutcome = {
406
588
  * @param sinkPlaces declared sink places (deadlock-freedom VC3)
407
589
  * @param solver the resolved z3 executable
408
590
  * @param timeoutMs per-invocation solver budget in milliseconds
591
+ * @param conditionalSinks conditional sink declarations (VER-014, deadlock-freedom VC3)
409
592
  */
410
- declare function checkCertificate(certificate: string | null, flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, invariants: readonly PInvariant[], sinkPlaces: ReadonlySet<Place<any>>, solver: Z3Solver, timeoutMs: number): Promise<CertificateCheckOutcome>;
593
+ declare function checkCertificate(certificate: string | null, flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, invariants: readonly PInvariant[], sinkPlaces: ReadonlySet<Place<any>>, solver: Z3Solver, timeoutMs: number, conditionalSinks?: readonly ConditionalSinks[], stateEquation?: boolean): Promise<CertificateCheckOutcome>;
411
594
  /**
412
595
  * The certificate-check script for the given inputs, exactly as
413
596
  * {@link checkCertificate} would send it (VER-013 script parity): what the
414
597
  * cross-language golden tests diff.
415
598
  */
416
- declare function vcScript(certificate: string, flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, invariants: readonly PInvariant[]): string;
599
+ declare function vcScript(certificate: string, flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, invariants: readonly PInvariant[], conditionalSinks?: readonly ConditionalSinks[], stateEquation?: boolean): string;
417
600
 
418
601
  /**
419
602
  * Name-correlation fragment classifier for the ν-aware state class graph
@@ -492,13 +675,19 @@ declare class SmtVerifier {
492
675
  private _property;
493
676
  private readonly _environmentPlaces;
494
677
  private readonly _sinkPlaces;
678
+ private readonly _conditionalSinks;
495
679
  private readonly _budgetPlaces;
496
680
  private _environmentMode;
497
681
  private _timeoutMs;
498
682
  private _certificateCheck;
499
683
  private _counterexampleReplay;
500
684
  private _semiflowInvariants;
685
+ private _stateEquation;
686
+ private _linearBound;
687
+ private _stateEquationPhase;
688
+ private _firingBound;
501
689
  private _nuMaxClasses;
690
+ private _enumerationMaxClasses;
502
691
  private _fragmentMode;
503
692
  private readonly _carrierPlaces;
504
693
  private _prioritySemantics;
@@ -510,10 +699,36 @@ declare class SmtVerifier {
510
699
  environmentPlaces(...places: EnvironmentPlace<any>[]): this;
511
700
  environmentMode(mode: EnvironmentAnalysisMode): this;
512
701
  /**
513
- * Declares expected sink (terminal) places for deadlock-freedom analysis.
514
- * Markings where any sink place has a token are not considered deadlocks.
702
+ * Declares expected sink (terminal) places for deadlock-freedom analysis
703
+ * (VER-002): a token resting in one is never stranded, and `TerminatesAtSink`
704
+ * asks whether one of them was reached.
515
705
  */
516
706
  sinkPlaces(...places: Place<any>[]): this;
707
+ /**
708
+ * Declares places where a token may rest **while `marker` holds a token**
709
+ * (VER-014) — a designed terminal such as a halt or pause marker, under which
710
+ * the work it interrupted legitimately stays where it was delivered.
711
+ *
712
+ * `DeadlockFree` then reads a quiescent marking against the union of the
713
+ * declared sinks, the markers, and every conditional set whose marker is marked:
714
+ * a token in `p` is stranded only when none of those excuse it. The marker
715
+ * itself is at rest whenever it is marked, so `sinkPlacesWhen(halt)` with no
716
+ * further places excuses exactly the halt token. Repeated calls for one marker
717
+ * accumulate; declarations for several markers union. `TerminatesAtSink` is
718
+ * unaffected and reads only {@link sinkPlaces}.
719
+ *
720
+ * ```ts
721
+ * SmtVerifier.forNet(net)
722
+ * .property(deadlockFree())
723
+ * .sinkPlaces(done) // may always rest
724
+ * .sinkPlacesWhen(halt, inbox, pending) // may rest once the run halted
725
+ * .sinkPlacesWhen(pause, inbox) // may rest while paused
726
+ * ```
727
+ *
728
+ * An unresolved marker or place contributes nothing, as an unresolved sink
729
+ * does: a mistyped marker makes the property stricter, never laxer.
730
+ */
731
+ sinkPlacesWhen(marker: Place<any>, ...places: Place<any>[]): this;
517
732
  /**
518
733
  * Declares ν-net budget places (NU-040): places whose token count bounds the
519
734
  * live correlation pool (they gate fresh-name minting). Declaring at least one
@@ -579,7 +794,95 @@ declare class SmtVerifier {
579
794
  * `Certificate check: not applicable (name-coloured encoding)`. Off by default so
580
795
  * reports stay byte-equal.
581
796
  */
582
- semiflowInvariants(enabled: boolean): this;
797
+ /**
798
+ * `'auto'` decides whether the semiflows would add **information to the
799
+ * encoding**, which is not the same question as whether they would appear in
800
+ * {@link SmtVerificationResult.invariants} for a caller who reads them.
801
+ *
802
+ * A complete basis spans every conservation law of the net, so a semiflow it
803
+ * spans constrains nothing further and IC3 gains nothing from it — that is why
804
+ * `'auto'` skips the enumeration there. But the basis is the *signed*
805
+ * null-space, and a law it spans need not appear in it in **non-negative**
806
+ * form; only the Farkas enumeration produces that. A caller inspecting the
807
+ * invariant list for a law of a given shape — "a non-negative law weighting the
808
+ * budget place and every running place positively" — can therefore find nothing
809
+ * on a net that plainly has one. Such a caller should ask for the union
810
+ * explicitly: `'auto'` is the setting to prefer for verification, not for
811
+ * harvesting.
812
+ */
813
+ semiflowInvariants(enabled: boolean | 'auto'): this;
814
+ /**
815
+ * Enables/disables the linear state-equation bound phase (VER-015; default:
816
+ * enabled). A reachability-safety property whose violating markings exceed some
817
+ * `y·M <= y·M0` with `y >= 0`, `y·C <= 0` is then proven structurally, from one
818
+ * linear query re-checked in exact integer arithmetic, before any fixpoint search.
819
+ * Disable it to force the IC3/PDR path — for its certificate, or to exercise the
820
+ * fixpoint engine itself.
821
+ */
822
+ linearBound(enabled: boolean): this;
823
+ /**
824
+ * Encodes the **state equation** with firing counters (VER-016; default:
825
+ * disabled — the encoding then carries places only).
826
+ *
827
+ * The flat encoding gains one counter `n_t` per flat transition and every
828
+ * transition rule conjoins the marking equation `M' = M0 + C·n'` for each place
829
+ * whose column is exact (no consume-all / reset arc, not injected). Every linear
830
+ * consequence of the marking equation — the equality laws of VER-005/VER-007
831
+ * **and** the inequality laws `y·M ≤ y·M0` (`y ≥ 0, y·C ≤ 0`) and their mixed-sign
832
+ * kin, which are what an *ordering* argument ("both join slots armed means every
833
+ * upstream stage has run, so nothing can still halt") looks like in linear
834
+ * arithmetic — is then available to Spacer as a fact rather than a lemma it has to
835
+ * invent. On a 50-place agent-dispatch workflow, proper completion under conditional
836
+ * sinks went from `unknown` after 120 s to `proven` in 1.5 s with this as the only
837
+ * change; a 53-place pipeline stage before a join, `unknown` at 300 s, proves in
838
+ * under a second.
839
+ *
840
+ * The cost is a larger state (places + transitions) and a slower witness search
841
+ * on genuinely violated properties (about 1.5× on the nets above), so it is opt-in.
842
+ * Soundness is unchanged: the counters are exact bookkeeping, the equation holds
843
+ * on every reachable state by construction (`Strengthening.lean`, the same shape
844
+ * as the equality laws), and the certificate check re-proves it against the raw
845
+ * step relation, whose only counter knowledge is the increment. Not applied to the
846
+ * name-coloured encoding or Route B, which the report says when it applies.
847
+ *
848
+ * Not {@link stateEquationPhase}, the VER-018 pre-phase (on by default) that can decide
849
+ * the property instead of the fixpoint query.
850
+ */
851
+ stateEquation(enabled: boolean): this;
852
+ /**
853
+ * Enables/disables the state-equation phase (VER-018; default: enabled).
854
+ *
855
+ * Before the fixpoint query, one linear query asks whether a marking the marking
856
+ * equation (`M = M0 + C·n`, `n ≥ 0`, an upper bound on a cleared place) admits violates
857
+ * the property; `unsat` proves it. A `sat` candidate is settled cheapest first: a run
858
+ * within its firing counts that reaches a violation (the counterexample), an initially
859
+ * marked trap it empties, or an inequality `a·M ≤ b` kept by the exact step relation
860
+ * that excludes it. The refinement is added and the query asked again.
861
+ *
862
+ * The proof `SE ∧ refinements` passes the certificate check before it is reported, and
863
+ * the report prints each refinement, e.g. `Merge/hasdata <= Merge/ready_0 +
864
+ * Merge/ready_1` on a workflow join. When nothing settles a candidate, the pipeline
865
+ * continues unchanged. Flat path only: skipped for a ν-net and under `ignore` with
866
+ * environment places. Runs within the full {@link timeout}; the certificate check gets
867
+ * its own.
868
+ *
869
+ * Not {@link stateEquation}, which adds firing counters inside the fixpoint encoding.
870
+ */
871
+ stateEquationPhase(enabled: boolean): this;
872
+ /**
873
+ * Enables/disables the firing-bound phase (VER-019; default: enabled).
874
+ *
875
+ * Weights `r ≥ 0` that every firing lowers by at least one bound every run by
876
+ * `K = r·M0` firings, so a bounded model check to depth `K` decides the property. The
877
+ * depth doubles from 8, which finds a short counterexample early. Without such weights
878
+ * the report names the transitions the marking equation lets repeat, and the fixpoint
879
+ * query runs.
880
+ *
881
+ * A proof carries no inductive invariant: the ranking is re-checked in exact integer
882
+ * arithmetic, and a counterexample is replayed. Runs after the state-equation phase, on
883
+ * the same nets, within half the {@link timeout}.
884
+ */
885
+ firingBound(enabled: boolean): this;
583
886
  /**
584
887
  * Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
585
888
  * Route B). When the symbolic name-aware graph would exceed this, the analysis
@@ -587,6 +890,26 @@ declare class SmtVerifier {
587
890
  * structurally bounded). Default 100_000.
588
891
  */
589
892
  nuMaxClasses(max: number): this;
893
+ /**
894
+ * Sets the class budget for the bounded state-space enumeration route
895
+ * (VER-017; default 50 000). `0` disables the route, so every query goes to
896
+ * the SMT pipeline.
897
+ *
898
+ * When the state-class graph closes within the budget the property is decided
899
+ * exactly — sound and complete over the timed semantics — and no solver runs.
900
+ * This is what makes a long pipeline tractable: IC3 needs a frame per stage and
901
+ * its cost climbs with the cube of the length, while enumeration is linear in
902
+ * the reachable state space. A forty-node chain (370 places, 1 967 classes)
903
+ * takes 410 s on the fixpoint path and 0.11 s here.
904
+ *
905
+ * The route declines when the graph exceeds the budget, and the SMT pipeline
906
+ * then runs unchanged — it can only add verdicts, never remove them. It is
907
+ * skipped for ν-nets, which have their own exact route (NU-050, Route B), for
908
+ * nets with environment places, whose injection the graph does not model, and
909
+ * for **timed** nets, where its verdict would be the weaker timed claim rather
910
+ * than the untimed one the encoders make (VER-004).
911
+ */
912
+ enumerationMaxClasses(max: number): this;
590
913
  /**
591
914
  * Selects the ν-net coloured-place fragment for Route B (NU-051). `base`
592
915
  * (default) admits the shipped mint → matched-join fragment only; `extended`
@@ -650,6 +973,31 @@ declare class SmtVerifier {
650
973
  * @throws Error if the net violates CORE-043 — verification rejects the same nets execution rejects.
651
974
  */
652
975
  verify(): Promise<SmtVerificationResult>;
976
+ /**
977
+ * Runs the linear state-equation bound query (VER-015) and re-checks its answer in
978
+ * exact integer arithmetic. Returns the bound as the report prints it when one
979
+ * separates the violation, `null` otherwise (no bound, solver inconclusive, or a
980
+ * model that failed the re-check — each named in the report). Never the last word:
981
+ * `null` hands over to the fixpoint query.
982
+ */
983
+ private linearBoundProof;
984
+ /**
985
+ * Whether environment places are registered but not modelled ([VER-006] `ignore`). A
986
+ * proof over a frozen environment is vacuous, so every route that can return `proven`
987
+ * refuses it. See {@link IGNORE_MODE_VACUITY_REASON}.
988
+ */
989
+ private get ignoresEnvironment();
990
+ /**
991
+ * Runs the state-equation phase (VER-018). Returns the final result when it decided
992
+ * the property — a `proven` only once the certificate check passed — and `null` when
993
+ * it stepped aside, with the reason in the report.
994
+ */
995
+ private stateEquationDecision;
996
+ /**
997
+ * Runs the firing-bound phase (VER-019). Returns the final result when it decided the
998
+ * property and `null` when it stepped aside, with the reason in the report.
999
+ */
1000
+ private firingBoundDecision;
653
1001
  /**
654
1002
  * ν-net soundness guard (NU-040, NU-050). Applied only when the net contains
655
1003
  * match (ν-join) transitions, and only to a proven/violated verdict (an
@@ -679,6 +1027,17 @@ interface EncodedScripts {
679
1027
  readonly certificate: string | null;
680
1028
  /** Whether `horn` is the name-coloured encoding. */
681
1029
  readonly coloured: boolean;
1030
+ /**
1031
+ * The linear state-equation bound query (VER-015), or `null` for a property with
1032
+ * no linear demand (the quiescence properties).
1033
+ */
1034
+ readonly bound: string | null;
1035
+ /**
1036
+ * The first query of the state-equation phase (VER-018), before any refinement, or
1037
+ * `null` where the phase does not run (the name-coloured encoding, a ν-net, `ignore`
1038
+ * with environment places, or the phase disabled).
1039
+ */
1040
+ readonly stateEquation: string | null;
682
1041
  }
683
1042
  /**
684
1043
  * `(define-fun Reachable ((x!0 Int) …) Bool true)`: the certificate stand-in the
@@ -744,6 +1103,11 @@ declare function runZ3Spacer(solver: Z3Solver, timeoutMs: number, smt2: string,
744
1103
  * 3. **Error**: `Error :- Reachable(M) ∧ violation(M)`; `(assert (not Error))`, so
745
1104
  * `sat` is PROVEN and `unsat` is VIOLATED
746
1105
  *
1106
+ * With the state equation (VER-016, {@link encodeNet}) the state is `(M, n)` — one
1107
+ * firing counter per flat transition — and every transition rule also conjoins
1108
+ * `M' = M0 + C·n'`, which hands Spacer every linear consequence of the marking
1109
+ * equation (the inequality conservation laws it cannot invent) at no enumeration cost.
1110
+ *
747
1111
  * The emitted script is byte-identical to the Rust reference (`smt_encoder.rs`) and
748
1112
  * the Java port for the same input: places in code-point order of their names, the
749
1113
  * property's places, sinks, env bounds and injections in place-index order,
@@ -754,16 +1118,48 @@ declare function runZ3Spacer(solver: Z3Solver, timeoutMs: number, smt2: string,
754
1118
  interface SmtEncoding {
755
1119
  /** The script text. */
756
1120
  readonly smt2: string;
757
- /** The number of flat places (the arity of `Reachable` in the flat encoding). */
1121
+ /** The number of flat places (the leading arguments of `Reachable` in the flat encoding). */
758
1122
  readonly placeCount: number;
1123
+ /**
1124
+ * The number of firing counters that follow the places in `Reachable` (VER-016):
1125
+ * one per flat transition when the state equation is encoded, else 0.
1126
+ */
1127
+ readonly counterCount: number;
1128
+ }
1129
+ /** Options of {@link encodeNet}. */
1130
+ interface EncodeOptions {
1131
+ /** Declared sink places (VER-002). */
1132
+ readonly sinkPlaces?: ReadonlySet<Place<any>>;
1133
+ /** Emit `:produce-proofs` and `(get-proof)` so an `unsat` reply carries the refutation the replay decodes. */
1134
+ readonly produceProofs?: boolean;
1135
+ /** Conditional sinks (VER-014); read by `deadlock-free` only. */
1136
+ readonly conditionalSinks?: readonly ConditionalSinks[];
1137
+ /**
1138
+ * Carry one firing counter per flat transition and conjoin the marking equation
1139
+ * `M' = M0 + C·n'` (VER-016) into every rule body. Off by default (scripts stay
1140
+ * byte-identical).
1141
+ */
1142
+ readonly stateEquation?: boolean;
759
1143
  }
760
1144
  /**
761
1145
  * Encodes the net and property as a HORN script.
762
1146
  *
763
1147
  * @param produceProofs emit `:produce-proofs` and `(get-proof)` so an `unsat` reply
764
1148
  * carries the refutation the replay decodes
1149
+ * @param conditionalSinks places where a token may rest while a marker is marked
1150
+ * (VER-014); read by `deadlock-free` only
765
1151
  */
766
- declare function encode(flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, invariants: readonly PInvariant[], sinkPlaces?: ReadonlySet<Place<any>>, produceProofs?: boolean): SmtEncoding;
1152
+ declare function encode(flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, invariants: readonly PInvariant[], sinkPlaces?: ReadonlySet<Place<any>>, produceProofs?: boolean, conditionalSinks?: readonly ConditionalSinks[]): SmtEncoding;
1153
+ /**
1154
+ * {@link encode} with named options. With `stateEquation` (VER-016) the state carries
1155
+ * one firing counter per flat transition after the places: `Reachable(M, n)`, the
1156
+ * initial fact has `n = 0`, transition `k`'s rule increments `n_k` and copies the
1157
+ * others, an injection rule copies them all, and every transition rule's body
1158
+ * conjoins `m'_p = M0_p + Σ_t C[p][t]·n'_t` for each place whose column is exact
1159
+ * (no consume-all / reset arc, not injected) together with `n' ≥ 0`. The error rule
1160
+ * quantifies the counters and constrains only the marking.
1161
+ */
1162
+ declare function encodeNet(flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, invariants: readonly PInvariant[], options?: EncodeOptions): SmtEncoding;
767
1163
  /**
768
1164
  * The net's one-step relation `T(M, M')` as one plain SMT-LIB2 formula over the free
769
1165
  * variables `m0..` / `m0p..`: the disjunction of every flat transition firing and
@@ -772,7 +1168,75 @@ declare function encode(flatNet: FlatNet, initialMarking: MarkingState, property
772
1168
  * path but omits the P-invariant conjuncts, so a certificate poisoned by a wrong
773
1169
  * invariant cannot re-certify itself.
774
1170
  */
775
- declare function encodeStepRelationSmt2(flatNet: FlatNet): string;
1171
+ declare function encodeStepRelationSmt2(flatNet: FlatNet, stateEquation?: boolean): string;
1172
+
1173
+ /**
1174
+ * @module linear-bound
1175
+ *
1176
+ * The linear state-equation bound (VER-015): a structural proof of a
1177
+ * reachability-safety property that needs no fixpoint search.
1178
+ *
1179
+ * Every reachable marking of the abstract net satisfies `M = M0 + C·σ` for some
1180
+ * firing count vector `σ ≥ 0`, so for any weighting `y ≥ 0` with `y·C ≤ 0` on every
1181
+ * transition, `y·M ≤ y·M0` holds along every run — a **decreasing** conservation
1182
+ * law, where the P-invariants of [VER-005] are the *equalities* `y·C = 0`. The
1183
+ * violation of a reachability-safety property is a lower demand on some places
1184
+ * (`m_p ≥ 1` for each place of an `unreachable`, `m_p ≥ k+1` for a `placeBound`);
1185
+ * if some `y` makes that demand exceed `y·M0`, no reachable marking meets it and the
1186
+ * property is proven.
1187
+ *
1188
+ * Finding `y` is one linear query in `QF_LIA`, answered by the same `z3` transport
1189
+ * as everything else ([VER-013]); the answer is then re-checked in exact integer
1190
+ * arithmetic (`y ≥ 0`, `y·C ≤ 0` per transition, `y·d ≥ y·M0 + 1`), so the proof
1191
+ * rests on the check, not on the solver. It closes exactly the class of proofs IC3
1192
+ * misses on pipeline-shaped nets: an ordering argument ("both join slots armed means
1193
+ * every upstream stage has run, so no halt is still possible") is a weighted count
1194
+ * bound, which Spacer's lemma generalisation does not invent over fifty variables
1195
+ * but a linear solver finds in milliseconds.
1196
+ *
1197
+ * Soundness needs the same guards as the equality laws: zero weight on every
1198
+ * consume-all / reset place (H1 — the fire relation is not linear there) and on
1199
+ * every injected environment place (H3' — injection breaks conservation).
1200
+ */
1201
+
1202
+ /** One linear bound `Σ weights[p]·m_p ≤ constant`, with the demand it separates. */
1203
+ interface LinearBound {
1204
+ /** `y`, one entry per flat place, all non-negative. */
1205
+ readonly weights: readonly bigint[];
1206
+ /** `y·M0`. */
1207
+ readonly constant: bigint;
1208
+ /** `y·d`, what the violating markings need at least; strictly above `constant`. */
1209
+ readonly demandValue: bigint;
1210
+ }
1211
+ /**
1212
+ * The violation's demand: flat place index → the least count a violating marking
1213
+ * holds there. `null` when the property is not a reachability-safety property, or
1214
+ * names no place the net resolves (the verifier refuses those before this runs).
1215
+ */
1216
+ declare function violationDemand(flatNet: FlatNet, property: SmtProperty): Map<number, number> | null;
1217
+ /**
1218
+ * The `QF_LIA` script asking for a separating `y`, or `null` when the property has
1219
+ * no linear demand. Byte-identical across the four implementations: places in flat
1220
+ * index order, one row per flat transition in net order, `(- k)` for a negative
1221
+ * literal, a lone term unwrapped.
1222
+ */
1223
+ declare function encodeLinearBound(flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty): string | null;
1224
+ /**
1225
+ * The weighting in a `sat` reply's model: `y_p` per flat place, zero where the model
1226
+ * is silent. `null` when the reply defines no `y`.
1227
+ */
1228
+ declare function decodeLinearBound(stdout: string, placeCount: number): bigint[] | null;
1229
+ /**
1230
+ * Re-proves the bound in exact integer arithmetic: `y ≥ 0`, zero on every H1/H3'
1231
+ * place, `y·C ≤ 0` on every flat transition, and `y·d ≥ y·M0 + 1`. Returns the bound
1232
+ * when every check passes and `null` otherwise — the verifier then continues to the
1233
+ * fixpoint query rather than trust the solver's model.
1234
+ */
1235
+ declare function checkLinearBoundExact(flatNet: FlatNet, initialMarking: MarkingState, property: SmtProperty, y: readonly bigint[]): LinearBound | null;
1236
+ /** `2*a + b <= 2` — the bound as the report prints it. */
1237
+ declare function formatLinearBound(flatNet: FlatNet, bound: LinearBound): string;
1238
+ /** `ready_0 + ready_1 + _halt >= 3` — the violation's weighted demand as the report prints it. */
1239
+ declare function formatLinearDemand(flatNet: FlatNet, property: SmtProperty, bound: LinearBound): string;
776
1240
 
777
1241
  /**
778
1242
  * @module counterexample-decoder
@@ -801,12 +1265,12 @@ interface DecodedTrace {
801
1265
  readonly note: string | null;
802
1266
  }
803
1267
  /** Decodes the states of a z3 reply; a note says so when none were found. */
804
- declare function decode(answer: string, flatNet: FlatNet): DecodedTrace;
1268
+ declare function decode(answer: string, flatNet: FlatNet, counterCount?: number): DecodedTrace;
805
1269
  /**
806
1270
  * Collects the ground `Reachable(...)` applications from a z3 refutation proof into
807
1271
  * a state set, in text order.
808
1272
  */
809
- declare function decodeStateSet(answer: string, flatNet: FlatNet): ReadonlySet<MarkingState>;
1273
+ declare function decodeStateSet(answer: string, flatNet: FlatNet, counterCount?: number): ReadonlySet<MarkingState>;
810
1274
 
811
1275
  /**
812
1276
  * @module abstract-replayer
@@ -907,7 +1371,7 @@ type ReplayOutcome = {
907
1371
  * search — non-dominated states only, the root included (see
908
1372
  * {@link ReplayOptions.nodeBudget}).
909
1373
  */
910
- declare function replayCounterexample(flatNet: FlatNet, initial: AbstractState, decodedStates: readonly AbstractState[], property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, options?: ReplayOptions): ReplayOutcome;
1374
+ declare function replayCounterexample(flatNet: FlatNet, initial: AbstractState, decodedStates: readonly AbstractState[], property: SmtProperty, sinkPlaces: ReadonlySet<Place<any>>, options?: ReplayOptions, conditionalSinks?: readonly ConditionalSinks[]): ReplayOutcome;
911
1375
 
912
1376
  /**
913
1377
  * Difference Bound Matrix (DBM) for Time Petri Net state class analysis.
@@ -940,10 +1404,35 @@ declare class DBM {
940
1404
  * Implements the 5-step Berthomieu-Diaz successor formula.
941
1405
  */
942
1406
  fireTransition(firedClock: number, newClockNames: readonly string[], newLowerBounds: number[], newUpperBounds: number[], persistentClocks: number[]): DBM;
1407
+ /**
1408
+ * The same zone with its clocks reordered: clock `k` of the result is clock
1409
+ * `order[k]` of this DBM. `order` must be a permutation of `0..clockCount()-1`.
1410
+ *
1411
+ * The state-class graph applies this to put every class's clocks in the one
1412
+ * canonical order (VER-010), so two arrivals at the same marking and zone whose
1413
+ * transitions became enabled in a different sequence share a key instead of
1414
+ * being counted as two classes. The reference row and column stay put; the
1415
+ * matrix is copied once, O(dim²) against the O(dim³) canonicalisation every
1416
+ * successor already pays.
1417
+ */
1418
+ permuted(order: readonly number[]): DBM;
943
1419
  /** Lets time pass: set all lower bounds to 0. */
944
1420
  letTimePass(): DBM;
945
1421
  private canonicalize;
946
1422
  equals(other: DBM): boolean;
1423
+ /**
1424
+ * The zone's identity for state-class dedup: the clock names and the FULL
1425
+ * canonical matrix, every difference bound included.
1426
+ *
1427
+ * {@link toString} prints only the per-clock projections `[lo, hi]`, and two
1428
+ * zones can agree on every projection while disagreeing on a difference
1429
+ * constraint `θi - θj <= c` — the class where one transition must fire no later
1430
+ * than another versus the class where either may go first. Keying on the
1431
+ * projections merges those, and since the graph explores only the first
1432
+ * arrival's successors, a marking reachable only from the second is lost: a
1433
+ * false `proven`. This key is what {@link equals} compares, rendered.
1434
+ */
1435
+ zoneKey(): string;
947
1436
  toString(): string;
948
1437
  }
949
1438
 
@@ -992,6 +1481,15 @@ interface BranchEdge {
992
1481
  readonly branchIndex: number;
993
1482
  readonly target: StateClass;
994
1483
  }
1484
+ /** Options for {@link StateClassGraph.build}. */
1485
+ interface StateClassGraphOptions {
1486
+ /**
1487
+ * Explore the **untimed** reachable set ([VER-004]): every clock gets `immediate()`'s
1488
+ * `[0, ∞)`, so any enabled transition may fire next and the graph holds exactly the
1489
+ * markings the untimed encoders reason about. No effect on an all-immediate net.
1490
+ */
1491
+ readonly untimed?: boolean;
1492
+ }
995
1493
  /**
996
1494
  * State Class Graph for Time Petri Net analysis.
997
1495
  *
@@ -1012,7 +1510,7 @@ declare class StateClassGraph {
1012
1510
  *
1013
1511
  * @throws Error if the net violates CORE-043 — analysis rejects the same nets execution rejects.
1014
1512
  */
1015
- static build(net: PetriNet, initialMarking: MarkingState, maxClasses: number, environmentPlaces?: Set<EnvironmentPlace<any>>, environmentMode?: EnvironmentAnalysisMode): StateClassGraph;
1513
+ static build(net: PetriNet, initialMarking: MarkingState, maxClasses: number, environmentPlaces?: Set<EnvironmentPlace<any>>, environmentMode?: EnvironmentAnalysisMode, options?: StateClassGraphOptions): StateClassGraph;
1016
1514
  stateClasses(): readonly StateClass[];
1017
1515
  size(): number;
1018
1516
  isComplete(): boolean;
@@ -1098,4 +1596,333 @@ declare class TimePetriNetAnalyzerBuilder {
1098
1596
  build(): TimePetriNetAnalyzer;
1099
1597
  }
1100
1598
 
1101
- export { type AbstractState, EnvironmentAnalysisMode as AnalysisEnvironmentMode, type BranchEdge, type CertificateCheckOutcome, type CertificateVc, DBM, DUMP_ENV, type DecodedTrace, type EncodedScripts, EnvironmentAnalysisMode, type FlatNet, type FlatTransition, IncidenceMatrix, type LivenessResult, MIN_Z3_VERSION, MarkingState, MarkingStateBuilder, PInvariant, type PrioritySemantics, type QueryProven, type QueryResult, type QueryUnknown, type QueryViolated, type ReplayOptions, type ReplayOutcome, type ReplayStep, type SmtEncoding, SmtProperty, SmtVerificationResult, SmtVerifier, StateClass, StateClassGraph, type StructuralCheckResult, TimePetriNetAnalyzer, TimePetriNetAnalyzerBuilder, type XorBranchAnalysis, type XorBranchInfo, type Z3Exit, Z3ProcessError, type Z3Reply, type Z3Solver, Z3Unavailable, type Z3Version, Z3_ENV, canonicalInvariantOrder, checkCertificate, computePInvariants, computePSemiflows, computeSCCs, decode, decodeStateSet, encode, encodeStepRelationSmt2, findMaximalTrapIn, findMinimalSiphons, findTerminalSCCs, flatNetIndexOf, flatNetPlaceCount, flatNetTransitionCount, flatTransition, flatten, formatZ3Version, isCoveredByInvariants, parseZ3Version, placeholderCertificate, replayCounterexample, resolveZ3, runZ3Spacer, runZ3Text, strengthenWithSemiflows, structuralCheck, vcScript, z3Available, z3SolverAt };
1599
+ /**
1600
+ * @module open-net/contract
1601
+ *
1602
+ * What a subnet promises when verified on its own, with its ports played by the environment
1603
+ * ([VER-022]).
1604
+ *
1605
+ * The **assumption**: the tokens the subnet holds before anything arrives, and arrival
1606
+ * groups, each delivering between `min` and `max` tokens onto its places at any point of the
1607
+ * run. Every bound is finite: a bound is both the runtime cap and the width of the claim.
1608
+ *
1609
+ * The **guarantee**: at every quiescent marking the count clauses hold, and tokens rest only
1610
+ * on clause, rest or environment places, or where a marked designed terminal excuses them;
1611
+ * every other place is internal and empty. Every run comes to rest unless
1612
+ * {@link OpenNetContractBuilder.requireTermination} is turned off.
1613
+ */
1614
+
1615
+ /**
1616
+ * The environment delivers between `min` and `max` tokens in total, each onto one of
1617
+ * `places`, each at any point of the run.
1618
+ */
1619
+ interface ArrivalGroup {
1620
+ readonly places: readonly Place<any>[];
1621
+ readonly min: number;
1622
+ readonly max: number;
1623
+ }
1624
+ /**
1625
+ * At every quiescent marking the tokens across `places` number between `min` and `max`
1626
+ * (`max` may be `Infinity`). A marked designed terminal waives `min`, never `max`: a halt
1627
+ * stops progress, it does not license a token too many.
1628
+ */
1629
+ interface CountClause {
1630
+ readonly name: string;
1631
+ readonly places: readonly Place<any>[];
1632
+ readonly min: number;
1633
+ readonly max: number;
1634
+ }
1635
+ /**
1636
+ * While `marker` holds a token, the clauses' lower bounds are waived and tokens may rest on
1637
+ * `excused`: the places where the work the marker interrupted was delivered. The marker
1638
+ * itself may always rest, as a conditional-sink marker may ([VER-014]).
1639
+ */
1640
+ interface DesignedTerminal {
1641
+ readonly marker: Place<any>;
1642
+ readonly excused: readonly Place<any>[];
1643
+ }
1644
+ /**
1645
+ * A subnet's contract: the environment it assumes and what it guarantees at quiescence
1646
+ * ([VER-022]). Build one with {@link OpenNetContract.builder}; check it with `verifyOpenNet`.
1647
+ *
1648
+ * ```ts
1649
+ * const contract = OpenNetContract.builder()
1650
+ * .initialMarking(m => m.tokens(idle, 1).tokens(budget, k))
1651
+ * .arrive(1, inData, inEmpty) // exactly one arrival on the input edge
1652
+ * .arriveAtMost(1, halt) // never or once
1653
+ * .expect('e3', 1, e3Data, e3Empty) // one of data / empty per outgoing edge, once it runs
1654
+ * .expect('idle', 1, idle)
1655
+ * .expect('budget', k, budget)
1656
+ * .expect('history', 1, done, skipped)
1657
+ * .terminal(halt, inData, inEmpty) // a halted run leaves the arrival where it was delivered
1658
+ * .terminal(skipped) // a skipped run writes no output edge at all
1659
+ * .build();
1660
+ * ```
1661
+ *
1662
+ * **A node that can skip needs its edge clauses conditional.** `expect('e3', 1, …)` alone
1663
+ * reports a node that rests having skipped the edge. Name the place that marks a skip as a
1664
+ * {@link OpenNetContractBuilder.terminal}: while it is marked the lower bounds are waived, and
1665
+ * the upper bounds still catch an edge written twice.
1666
+ *
1667
+ * **A subnet that asks something of its neighbours needs an environment.** Alone, a node that
1668
+ * sends a request and waits quiesces with the request outstanding. Give the contract the
1669
+ * transitions the neighbours fire; their own places are never counted as stranded.
1670
+ *
1671
+ * ```ts
1672
+ * // A node that runs again on every answer, against an environment that answers twice.
1673
+ * const again = Transition.builder('env/again').inputs(one(request), one(rounds))
1674
+ * .outputs(outPlace(reply)).build();
1675
+ * const end = Transition.builder('env/end').inputs(one(request)).outputs(outPlace(ended)).build();
1676
+ *
1677
+ * const agent = OpenNetContract.builder()
1678
+ * .initialMarking(m => m.tokens(rounds, 2)) // the environment's own budget
1679
+ * .arrive(1, inPlace)
1680
+ * .expectBetween('done', 0, 1, done) // it may end without finishing
1681
+ * .environment(again, end)
1682
+ * .build();
1683
+ * ```
1684
+ *
1685
+ * An environment transition is never executed, so it needs no action.
1686
+ */
1687
+ declare class OpenNetContract {
1688
+ /** Tokens the subnet holds before anything arrives: its own resources and any shared pool it borrows from. */
1689
+ readonly initialMarking: MarkingState;
1690
+ readonly arrivals: readonly ArrivalGroup[];
1691
+ readonly clauses: readonly CountClause[];
1692
+ /** Places that may hold any number of tokens at quiescence. */
1693
+ readonly rest: readonly Place<any>[];
1694
+ readonly terminals: readonly DesignedTerminal[];
1695
+ /** Transitions the environment fires: neighbours that react to what the subnet sends. */
1696
+ readonly environment: readonly Transition[];
1697
+ /** Whether every run must come to rest. */
1698
+ readonly requiresTermination: boolean;
1699
+ /** @internal Use {@link OpenNetContract.builder}. */
1700
+ constructor(key: symbol, initialMarking: MarkingState, arrivals: readonly ArrivalGroup[], clauses: readonly CountClause[], rest: readonly Place<any>[], terminals: readonly DesignedTerminal[], environment: readonly Transition[], requiresTermination: boolean);
1701
+ static builder(): OpenNetContractBuilder;
1702
+ /**
1703
+ * Every place the initial marking, an arrival group, a clause, the rest set or a terminal
1704
+ * marker names, then every place an environment transition touches, in first-mention
1705
+ * order: the places a port trace reports. A terminal's excused places are not included;
1706
+ * `closeOpenNet` adds them to the closed net itself.
1707
+ */
1708
+ places(): Place<any>[];
1709
+ /** The contract as the report prints it, one line per part. */
1710
+ describe(): string[];
1711
+ }
1712
+ declare class OpenNetContractBuilder {
1713
+ private _initialMarking;
1714
+ private readonly _arrivals;
1715
+ private readonly _clauses;
1716
+ private readonly _rest;
1717
+ private readonly _terminals;
1718
+ private readonly _environment;
1719
+ private _requiresTermination;
1720
+ /** Tokens the subnet holds before anything arrives. */
1721
+ initialMarking(marking: MarkingState): this;
1722
+ initialMarking(configurator: (builder: MarkingStateBuilder) => void): this;
1723
+ /** The environment delivers exactly `count` tokens, each onto one of `places`, at any point of the run. */
1724
+ arrive(count: number, ...places: Place<any>[]): this;
1725
+ /** The environment delivers at most `max` tokens, possibly none. `arriveAtMost(1, halt)` is "never or once". */
1726
+ arriveAtMost(max: number, ...places: Place<any>[]): this;
1727
+ /** The environment delivers between `min` and `max` tokens in total, each onto one of `places`, at any point of the run. */
1728
+ arriveBetween(min: number, max: number, ...places: Place<any>[]): this;
1729
+ /** At every quiescent marking, exactly `count` tokens across `places`. */
1730
+ expect(name: string, count: number, ...places: Place<any>[]): this;
1731
+ /** At every quiescent marking, between `min` and `max` tokens across `places`; `max` may be `Infinity`. */
1732
+ expectBetween(name: string, min: number, max: number, ...places: Place<any>[]): this;
1733
+ /** Places that may hold any number of tokens at quiescence. */
1734
+ rest(...places: Place<any>[]): this;
1735
+ /**
1736
+ * A designed terminal: while `marker` holds a token, lower bounds are waived and tokens may
1737
+ * rest on `excused`. Repeated calls for one marker accumulate.
1738
+ */
1739
+ terminal(marker: Place<any>, ...excused: Place<any>[]): this;
1740
+ /**
1741
+ * Transitions the environment fires: a neighbour that reacts to what the subnet sends, such
1742
+ * as a tool answering a request. An arrival group cannot say that: its tokens do not wait
1743
+ * for a request.
1744
+ *
1745
+ * They join the closed net unchanged and are marked as environment steps in the port trace.
1746
+ * A place only they touch belongs to the environment and may hold tokens at quiescence; a
1747
+ * place they share with the subnet is a port, judged like any other. Their actions never
1748
+ * run, so one that declares outputs may keep `passthrough()`.
1749
+ */
1750
+ environment(...transitions: Transition[]): this;
1751
+ /** Whether every run must come to rest (default `true`). */
1752
+ requireTermination(required: boolean): this;
1753
+ build(): OpenNetContract;
1754
+ }
1755
+
1756
+ /**
1757
+ * @module open-net/closure
1758
+ *
1759
+ * An open net closed by the environment its contract describes ([VER-022]).
1760
+ *
1761
+ * Each arrival group becomes a source place holding the tokens it must deliver, one
1762
+ * transition per target place moving a token across, and a second source for the optional
1763
+ * part whose tokens may also be declined. The contract's environment transitions join
1764
+ * unchanged. Every interleaving of environment steps with the subnet's firings is a run of
1765
+ * the closed net, which is quiescent only once the environment has delivered what it must
1766
+ * and decided about the rest. Every route then verifies a plain net.
1767
+ *
1768
+ * Ordinary places, not the environment places of [VER-006]: those never run dry, so a net
1769
+ * with one is never quiescent and every quiescence property would hold vacuously.
1770
+ */
1771
+
1772
+ /** What an environment transition of the closure does, for the port trace. */
1773
+ type EnvironmentStep = {
1774
+ readonly kind: 'arrival';
1775
+ readonly group: number;
1776
+ readonly place: string;
1777
+ } | {
1778
+ readonly kind: 'decline';
1779
+ readonly group: number;
1780
+ }
1781
+ /** One of the contract's own environment transitions. */
1782
+ | {
1783
+ readonly kind: 'transition';
1784
+ };
1785
+ /** An open net and its environment, as one closed net. */
1786
+ interface ClosedNet {
1787
+ readonly net: PetriNet;
1788
+ readonly initialMarking: MarkingState;
1789
+ /** Each environment transition by name, with what it does. */
1790
+ readonly environment: ReadonlyMap<string, EnvironmentStep>;
1791
+ /**
1792
+ * Places only the contract's environment transitions touch: the environment's own state.
1793
+ * A token left on one at quiescence is never stranded.
1794
+ */
1795
+ readonly environmentPlaces: readonly Place<any>[];
1796
+ /**
1797
+ * Places the contract names that no arc touches, in contract order. They join the closed
1798
+ * net as places of their own, so every route resolves them. A clause over a place nothing
1799
+ * writes then counts zero there, which is the finding, not an error.
1800
+ */
1801
+ readonly undeclared: readonly string[];
1802
+ }
1803
+ /**
1804
+ * Closes `net` with the environment of `contract`: its environment transitions, and for
1805
+ * arrival group `i` a source `env:arrivals[i]` holding `min` tokens and a source
1806
+ * `env:optional[i]` holding `max − min`, with transitions `env:arrive[i]:<place>` /
1807
+ * `env:arrive?[i]:<place>` moving a token onto each of the group's places, and
1808
+ * `env:decline[i]` discarding an optional one.
1809
+ *
1810
+ * @throws when a name the closure would add is already taken in `net`
1811
+ */
1812
+ declare function closeOpenNet(net: PetriNet, contract: OpenNetContract): ClosedNet;
1813
+
1814
+ /**
1815
+ * @module open-net/result
1816
+ *
1817
+ * What `verifyOpenNet` returns ([VER-022]), and how a witness becomes a port trace.
1818
+ */
1819
+
1820
+ /** Which part of the contract a violation breaks. */
1821
+ type ContractViolationKind =
1822
+ /** A count clause: too few tokens across its places at quiescence with no terminal marked, or too many. */
1823
+ 'clause'
1824
+ /** A token rests where the contract lets none rest: on an internal place, or on one only an unmarked terminal excuses. */
1825
+ | 'stranded'
1826
+ /** A run that never comes to rest: a reachable cycle. */
1827
+ | 'termination';
1828
+ /** A token-count change on one contract place. */
1829
+ interface PortChange {
1830
+ readonly place: string;
1831
+ readonly delta: number;
1832
+ }
1833
+ /** A firing that touches the subnet's boundary: an environment step, or a change on a contract place. */
1834
+ interface PortStep {
1835
+ /** The firing's position in {@link ContractViolation.transitions}, counting from 1. */
1836
+ readonly step: number;
1837
+ readonly transition: string;
1838
+ /**
1839
+ * Set when the environment fired it: `arrival` or `decline` for an arrival group,
1840
+ * `transition` for one of the contract's environment transitions. `null` for the subnet.
1841
+ */
1842
+ readonly environment: EnvironmentStep['kind'] | null;
1843
+ /** Token changes on the contract's places, in the contract's order. */
1844
+ readonly changes: readonly PortChange[];
1845
+ }
1846
+ /** One broken part of the contract, with a firing sequence that breaks it. */
1847
+ interface ContractViolation {
1848
+ readonly kind: ContractViolationKind;
1849
+ /**
1850
+ * The clause's name, a stranded place's name, or `termination`. The SMT route reports one
1851
+ * stranding for the whole query, naming every stranded place comma-separated.
1852
+ */
1853
+ readonly subject: string;
1854
+ /** What was found, in words. */
1855
+ readonly detail: string;
1856
+ /** The firing sequence from the initial marking, environment transitions included. */
1857
+ readonly transitions: readonly string[];
1858
+ /** The marking before the first firing and after each one, when the route has them in order. */
1859
+ readonly markings: readonly MarkingState[];
1860
+ /** For `termination`, the index into {@link transitions} where the repeating cycle starts. */
1861
+ readonly cycleStart: number | null;
1862
+ /** The firings of {@link transitions} that touch the boundary. */
1863
+ readonly portTrace: readonly PortStep[];
1864
+ /**
1865
+ * Whether {@link transitions} is a real firing sequence in order. Always on the graph
1866
+ * route; on the SMT route it is the counterexample replay's outcome ([VER-003]).
1867
+ */
1868
+ readonly confirmed: boolean;
1869
+ }
1870
+ /** Which route decided the verdict. */
1871
+ type OpenNetRoute = 'enumeration' | 'smt';
1872
+ /** The outcome of `verifyOpenNet`. */
1873
+ interface OpenNetResult {
1874
+ /** `proven`, `violated` (see {@link violations}), or `unknown` with the reason. */
1875
+ readonly verdict: Verdict;
1876
+ /**
1877
+ * Every broken part found. The graph route lists clauses in contract order, then stranded
1878
+ * places by name, then termination; the SMT route asks for stranding first, so it lists
1879
+ * that, then clauses in contract order, then termination.
1880
+ */
1881
+ readonly violations: readonly ContractViolation[];
1882
+ readonly route: OpenNetRoute;
1883
+ /** Classes the state-class graph explored; `0` when it was skipped. */
1884
+ readonly classCount: number;
1885
+ /** Whether the state-class graph closed within its budget. */
1886
+ readonly graphComplete: boolean;
1887
+ readonly report: string;
1888
+ /** The subnet closed by its environment: what every route verified. */
1889
+ readonly closedNet: PetriNet;
1890
+ readonly closedMarking: MarkingState;
1891
+ readonly elapsedMs: number;
1892
+ }
1893
+
1894
+ /**
1895
+ * @module open-net/verify-open-net
1896
+ *
1897
+ * A subnet verified on its own against a contract, with its ports played by the environment
1898
+ * ([VER-022]). The closed net's untimed state-class graph decides exactly when it closes; a
1899
+ * violation it finds stands either way, and otherwise the SMT pipeline gets the contract.
1900
+ * Composing the per-subnet proofs into a claim about a whole net is the caller's theorem.
1901
+ */
1902
+
1903
+ /** Options for {@link verifyOpenNet}. */
1904
+ interface OpenNetOptions {
1905
+ /** Class budget for the state-class graph (default 50 000, as for [VER-017]). `0` skips the graph. */
1906
+ readonly maxClasses?: number;
1907
+ /** Whether to ask the SMT pipeline when the graph does not close (default `true`). */
1908
+ readonly smt?: boolean;
1909
+ /** Configures each `SmtVerifier` the SMT route builds, e.g. `v => v.timeout(120_000).stateEquation(true)`. */
1910
+ readonly configureSmt?: (verifier: SmtVerifier) => SmtVerifier;
1911
+ /** Time for the firing-bound query that decides termination on the SMT route (default 60 s). */
1912
+ readonly terminationTimeoutMs?: number;
1913
+ }
1914
+ /**
1915
+ * Verifies `net` in isolation against `contract` ([VER-022]).
1916
+ *
1917
+ * `proven` means that, in every run of the environment the contract assumes, every
1918
+ * quiescent marking meets the contract, and (unless termination is waived) every run comes
1919
+ * to rest. The claim is untimed, priority-blind and value-blind, like every route's
1920
+ * ([VER-004]). `violated` lists every broken part with a shortest witness, and
1921
+ * `unknown` says why neither route decided.
1922
+ *
1923
+ * @throws when the net violates CORE-043, as every verifier does, or when the closure's
1924
+ * names collide with the net's
1925
+ */
1926
+ declare function verifyOpenNet(net: PetriNet, contract: OpenNetContract, options?: OpenNetOptions): Promise<OpenNetResult>;
1927
+
1928
+ export { type AbstractState, EnvironmentAnalysisMode as AnalysisEnvironmentMode, type ArrivalGroup, type BranchEdge, type CertificateCheckOutcome, type CertificateVc, type ClassView, type ClosedNet, type ConditionalSinks, type ContractViolation, type ContractViolationKind, type CountClause, DBM, DUMP_ENV, type DecodedTrace, type DesignedTerminal, type EncodeOptions, type EncodedScripts, EnvironmentAnalysisMode, type EnvironmentStep, type FlatNet, type FlatTransition, IncidenceMatrix, type LinearBound, type LivenessResult, MIN_Z3_VERSION, MarkingState, MarkingStateBuilder, NOTE_ENUMERATED, OpenNetContract, OpenNetContractBuilder, type OpenNetOptions, type OpenNetResult, type OpenNetRoute, PInvariant, type PortChange, type PortStep, type PrioritySemantics, type QueryProven, type QueryResult, type QueryUnknown, type QueryViolated, type ReplayOptions, type ReplayOutcome, type ReplayStep, type ScgOutcome, type SmtEncoding, SmtProperty, SmtVerificationResult, SmtVerifier, StateClass, StateClassGraph, type StateClassGraphOptions, type StructuralCheckResult, TimePetriNetAnalyzer, TimePetriNetAnalyzerBuilder, Verdict, type XorBranchAnalysis, type XorBranchInfo, type Z3Exit, Z3ProcessError, type Z3Reply, type Z3Solver, Z3Unavailable, type Z3Version, Z3_ENV, canonicalInvariantOrder, checkCertificate, checkLinearBoundExact, closeOpenNet, computePInvariants, computePSemiflows, computeSCCs, decideOverClasses, decode, decodeLinearBound, decodeStateSet, describeSinks, encode, encodeLinearBound, encodeNet, encodeStepRelationSmt2, findMaximalTrapIn, findMinimalSiphons, findTerminalSCCs, flatNetIndexOf, flatNetPlaceCount, flatNetTransitionCount, flatTransition, flatten, formatLinearBound, formatLinearDemand, formatZ3Version, isCoveredByInvariants, isUntimed, parseZ3Version, placeholderCertificate, replayCounterexample, resolveZ3, rethrowIfProgrammingError, runZ3Spacer, runZ3Text, strandedPlaces, strandingExcuses, strandsToken, strengthenWithSemiflows, structuralCheck, vcScript, verifyOpenNet, verifyViaStateClassGraph, violationDemand, z3Available, z3SolverAt };