# API Tour — wasm4pm-compat
This document is a guided walk through the most-used API surfaces. Each section
has working code snippets. All snippets use the `prelude` or explicit module paths
— import whichever fits your style.
---
## 1. The Evidence lifecycle
Evidence moves through a strictly-ordered, type-enforced lifecycle. Each stage
is an empty enum used as a `PhantomData` tag inside `Evidence<T, State, W>`.
```
+-- refuse ------+---> Refused (terminal; carries a named law)
```
Stage types live in `wasm4pm_compat::state`:
| `Raw` | Untrusted input, just received |
| `Parsed` | Format-decoder accepted the shape; not yet judged |
| `Admitted` | Passed an `Admit` gate against a named `Witness` |
| `Refused` | Terminal; a specific named law was violated |
| `Projected` | Result of a named lossy projection |
| `Exportable` | Cleared to leave the crate as an external value |
| `Receipted` | Wrapped in a provenance receipt; ready for `wasm4pm` |
### Building Raw evidence
```rust
use wasm4pm_compat::evidence::Evidence;
use wasm4pm_compat::witness::Ocel20;
// Only Raw is freely constructable.
let raw = Evidence::<_, _, Ocel20>::raw("some-bytes");
assert_eq!(raw.value, "some-bytes");
```
### Fast-reject before parsing
```rust
use wasm4pm_compat::evidence::Evidence;
use wasm4pm_compat::witness::Xes1849;
let raw: Evidence<&[u8], _, Xes1849> = Evidence::raw(b"".as_ref());
let refused = raw.refuse(); // Raw -> Refused, before even parsing
let _ = refused.as_refused_value();
```
### Advancing Raw -> Parsed -> Refused
```rust
use wasm4pm_compat::evidence::Evidence;
use wasm4pm_compat::witness::Xes1849;
// Parsed but structurally inadmissible (e.g. no traces).
let refused = Evidence::<_, _, Xes1849>::raw(vec![])
.into_parsed()
.into_refused();
let _ = refused.into_refused_value();
```
### The only path to Admitted: an `Admit` impl
```rust
use wasm4pm_compat::admission::{Admit, Admission, Refusal};
use wasm4pm_compat::evidence::Evidence;
use wasm4pm_compat::state::Raw;
use wasm4pm_compat::witness::Ocel20;
enum NonEmptyLog {}
impl Admit for NonEmptyLog {
type Raw = String;
type Admitted = String;
type Reason = &'static str;
type Witness = Ocel20;
fn admit(raw: Evidence<String, Raw, Ocel20>)
-> Result<Admission<String, Ocel20>, Refusal<&'static str, Ocel20>>
{
if raw.value.is_empty() {
Err(Refusal::new("EmptyLog"))
} else {
Ok(Admission::new(raw.value))
}
}
}
let ev = NonEmptyLog::admit(Evidence::raw("log-data".to_string()))
.unwrap()
.into_evidence();
assert_eq!(ev.into_inner(), "log-data");
```
`Evidence::sealed` (the `Admitted` constructor) is `pub(crate)`. External code
reaches `Admitted` only through `Admission::into_evidence()`, which is only
returned by an `Admit::admit` implementation. This is the one-way door.
### From Admitted to the terminal stages
```rust
use wasm4pm_compat::admission::Admission;
use wasm4pm_compat::witness::Ocel20;
// Path A: Admitted -> Projected -> Receipted
let receipted_via_proj = Admission::<_, Ocel20>::new("v")
.into_evidence()
.into_projected()
.into_receipted();
// Path B: Admitted -> Exportable -> Receipted
let receipted_via_export = Admission::<_, Ocel20>::new("v")
.into_evidence()
.into_exportable()
.into_receipted();
// Path C: Admitted -> Receipted (direct)
let receipted_direct = Admission::<_, Ocel20>::new("v")
.into_evidence()
.into_receipted();
assert_eq!(receipted_via_proj.value, "v");
assert_eq!(receipted_via_export.value, "v");
assert_eq!(receipted_direct.value, "v");
```
### Builder ergonomics: `PowlBuilder`
Construction stays terse without weakening the type law. `PowlBuilder` is a fluent arena
builder for POWL models; its terminal `build()` is *checked* and returns a named
`PowlRefusal` on malformed input (use `build_unchecked()` only when the shape is already
known lawful).
```rust
use wasm4pm_compat::powl::{PowlBuilder, PowlRefusal};
// A lawful choice with two branches builds.
let model = PowlBuilder::new()
.atom("a")
.atom("b")
.choice("c", &["a", "b"])
.build();
assert!(model.is_ok());
// A choice with a single branch is refused — by name, not by panic.
let bad = PowlBuilder::new()
.atom("a")
.choice("c", &["a"])
.build();
assert!(matches!(bad, Err(PowlRefusal::InvalidChoiceArity { .. })));
```
Other builder surfaces follow the same shape across the canon: `Event::new(…)`,
`Trace::from_events(…)`, `EventLog::from_traces(…)`, and the OCEL link/change builders.
---
## 2. The Witness marker system
A witness is a zero-sized empty enum that names a standard, paper, API grammar,
Rust law, or internal bridge. It is the type-level authority a value is admitted,
projected, or graduated against.
### Why witnesses exist
`Admission<T, Ocel20>` and `Admission<T, Xes1849>` are different types. You
cannot pass an OCEL admission where an XES admission is expected. The compiler
enforces the boundary at zero runtime cost.
### The `Witness` trait
```rust
use wasm4pm_compat::witness::{Witness, WitnessFamily, Ocel20};
assert_eq!(Ocel20::KEY, "ocel-2.0");
assert_eq!(Ocel20::TITLE, "OCEL 2.0");
assert_eq!(Ocel20::YEAR, Some(2023));
assert_eq!(Ocel20::FAMILY, WitnessFamily::Standard);
```
### Witness families
| `Standard` | `Ocel20`, `Xes1849`, `XesLifecycleExt`, `OcelObjectType` |
| `Paper` | `WfNetSoundnessPaper`, `PowlPaper`, `InductiveMiner`, `AlphaMiner` |
| `ApiGrammar` | `Pm4pyApiGrammar`, `PmaxConsumerGrammar` |
| `RustLaw` | `RustTypestateLaw` |
| `InternalBridge` | `Wasm4pmBridge` |
### All canonical witnesses (as of this writing)
`Ocel20`, `Xes1849`, `Pm4pyApiGrammar`, `PmaxConsumerGrammar`, `PowlPaper`,
`ObjectCentricPetriNetPaper`, `WfNetSoundnessPaper`, `OcpqPaper`, `DeclareFamily`,
`PredictiveMonitoringFamily`, `ReceiptFamily`, `RustTypestateLaw`, `Wasm4pmBridge`,
`YawlPaper`, `SeparableWfNetPaper`, `WorkflowPatternsPaper`, `InductiveMiner`,
`DeclareConstraints`, `AlignmentPaper`, `OcPetriNets`, `LogSkeleton`, `AlphaMiner`,
`XesLifecycleExt`, `XesConceptExt`, `OcelObjectType`, `OcelEventType`,
`OcelAttributeType`, `WfNet2Powl`, `DivergenceWitness`, `ConvergenceWitness`.
### Using sub-witnesses for fine-grained authority
```rust
use wasm4pm_compat::admission::Admission;
use wasm4pm_compat::witness::{Ocel20, OcelObjectType};
// Tag the overall log against the full OCEL 2.0 standard.
let _log_admission: Admission<String, Ocel20> = Admission::new("full-log".to_string());
// Tag a specific object-type declaration against the object-type namespace authority.
let _type_admission: Admission<String, OcelObjectType> = Admission::new("order".to_string());
```
---
## 3. The LossPolicy chain
Some translations between process-evidence shapes cannot be lossless. Flattening
an OCEL log to XES drops all object types except the chosen case notion. This
module makes that loss accountable.
### The three policies
```rust
use wasm4pm_compat::loss::LossPolicy;
// Refuse any projection that would drop evidence.
let _ = LossPolicy::RefuseLoss;
// Allow loss, but only under an explicitly named projection.
let _ = LossPolicy::AllowNamedProjection;
// Allow loss and require a LossReport enumerating what was dropped.
let _ = LossPolicy::AllowLossWithReport;
```
### The `Project` trait
```rust
use wasm4pm_compat::loss::{LossPolicy, LossReport, Project, ProjectionName};
enum OcelShape {}
enum XesShape {}
struct Flatten { dropped: Vec<&'static str> }
impl Project for Flatten {
type From = OcelShape;
type To = XesShape;
type Lost = Vec<&'static str>;
type Reason = &'static str;
fn project(
self,
policy: LossPolicy,
) -> Result<LossReport<OcelShape, XesShape, Vec<&'static str>>, &'static str> {
if !self.dropped.is_empty() && policy.is_refusing() {
return Err("FlatteningLoss");
}
Ok(LossReport::new(
ProjectionName("ocel-flatten-to-xes:by-order"),
policy,
self.dropped,
))
}
}
// Refused path
let refused = Flatten { dropped: vec!["item"] }.project(LossPolicy::RefuseLoss);
assert_eq!(refused, Err("FlatteningLoss"));
// Reporting path
let report = Flatten { dropped: vec!["item"] }
.project(LossPolicy::AllowLossWithReport)
.unwrap();
assert!(!report.is_lossless());
assert_eq!(report.lost, vec!["item"]);
let summary = report.summary("DroppedObjectTypeLinks");
assert_eq!(summary.projection().as_str(), "ocel-flatten-to-xes:by-order");
```
### Compile-time loss categories with `NamedLossConst`
When the loss category is known at compile time, bake it into the type:
```rust
use wasm4pm_compat::loss::NamedLossConst;
type DroppedLinks = NamedLossConst<"DroppedObjectTypeLinks">;
type FlattenedRel = NamedLossConst<"FlattenedMultiObjectRelation">;
// Two distinct loss categories are two distinct types.
assert_eq!(DroppedLinks::NAME, "DroppedObjectTypeLinks");
assert_ne!(DroppedLinks::NAME, FlattenedRel::NAME);
```
### Multi-step loss accounting with `LossChain`
```rust
use wasm4pm_compat::loss::{LossChain, NamedLoss, ProjectionName};
let mut chain = LossChain::new();
chain.push(NamedLoss::new(
ProjectionName("ocel-flatten-to-xes:by-order"),
"DroppedObjectTypeLinks",
));
chain.push(NamedLoss::new(
ProjectionName("xes-to-dfg:aggregate"),
"FlattenedTimestamps",
));
assert_eq!(chain.len(), 2);
assert!(!chain.is_lossless());
```
---
## 4. The receipt pattern
Receipts are provenance-bearing evidence envelopes. They carry a witness label,
a content digest, and a replay hint. They are **structure only** — the crate
never hashes, signs, or verifies; it carries and validates the form.
### `ReceiptShape` — the minimal form
```rust
use wasm4pm_compat::receipt::{Digest, ReplayHint, ReceiptShape};
let r = ReceiptShape::new(
"discovery-run",
Digest::new("blake3:abc123"),
ReplayHint::new("rerun:plan#1"),
);
assert!(r.is_well_shaped());
```
### `ReceiptEnvelope` — adds a subject field
```rust
use wasm4pm_compat::receipt::{Digest, ReplayHint, ReceiptEnvelope, ReceiptRefusal};
// Safe constructor: refuses with a named law if any field is empty.
let ok = ReceiptEnvelope::try_from_parts(
"case-42",
"discovery-run",
Digest::new("blake3:abc123"),
ReplayHint::new("wasm4pm://intake/case-42"),
).unwrap();
assert!(ok.is_well_shaped());
// Named refusal: empty subject.
let bad = ReceiptEnvelope::try_from_parts(
"",
"discovery-run",
Digest::new("blake3:abc123"),
ReplayHint::new("wasm4pm://intake/case-42"),
);
assert_eq!(bad, Err(ReceiptRefusal::MissingSubject));
```
### `ReceiptChain` — multi-step provenance
```rust
use wasm4pm_compat::receipt::{
Digest, ReplayHint, ReceiptChain, ReceiptEnvelope, ReceiptRefusal,
};
let root = ReceiptEnvelope::new("root", "w", Digest::new("d0"), ReplayHint::new("h0"));
let mut chain = ReceiptChain::try_new("run-001", vec![root]).unwrap();
let step = ReceiptEnvelope::new("step-1", "w", Digest::new("d1"), ReplayHint::new("h1"));
chain.extend_with(step).unwrap();
assert_eq!(chain.len(), 2);
assert_eq!(chain.root().subject, "root");
assert_eq!(chain.tip().subject, "step-1");
// Empty chain is refused with a named law.
assert_eq!(
ReceiptChain::try_new("bad", vec![]),
Err(ReceiptRefusal::EmptyChain)
);
```
### `ReceiptChainConst<N>` — fixed-arity, stack-allocated
```rust
use wasm4pm_compat::receipt::{
Digest, ReplayHint, ReceiptChainConst, ReceiptEnvelope,
};
let a = ReceiptEnvelope::new("root", "w", Digest::new("d0"), ReplayHint::new("h0"));
let b = ReceiptEnvelope::new("step", "w", Digest::new("d1"), ReplayHint::new("h1"));
let chain = ReceiptChainConst::try_new("run-001", [a, b]).unwrap();
// Arity is a compile-time constant:
assert_eq!(chain.arity(), 2);
```
### Chain-sealing admission (`SealingAdmit` seam, v26.6.14)
The `SealingAdmit` trait is a companion seam *beside* `Admit` — it threads a
runtime [`RuntimeSeal`] (a recomputed BLAKE3 chain digest) into the verdict
atomically. The consumer supplies the BLAKE3 fold; `compat` proves shape + chain.
Running example: `cargo run --example sealing_admit_chain`
```rust,no_run
use wasm4pm_compat::admission::{recompute_and_match, RuntimeSeal, SealedAdmission};
use wasm4pm_compat::receipt::Digest;
use wasm4pm_compat::witness::AffidavitReceiptChain;
let claimed = Digest::new("blake3:abc");
// Consumer supplies the fold; compat proves the match.
## 5. Process model types overview
All process model types are structure only. They carry shape; they do not execute.
### `eventlog` — case-centric event log
Core types: `Event`, `Trace`, `EventLog`, `EventStream`.
```rust
use wasm4pm_compat::eventlog::{Event, EventLog, Trace};
let trace = Trace::new("case-1", [Event::new("start").at_ns(0)]);
let log = EventLog::from_traces([trace]);
assert_eq!(log.trace_count(), 1);
assert!(log.validate().is_ok());
```
### `ocel` — object-centric event log
Core types: `Object`, `OcelEvent`, `EventObjectLink`, `ObjectObjectLink`,
`ObjectChange`, `OcelLog`.
```rust
use wasm4pm_compat::ocel::{Object, OcelEvent, OcelLog};
let log = OcelLog::new(
[Object::new("o1", "order")],
[OcelEvent::new("e1", "create").at_ns(0)],
[], [], [],
);
assert!(log.validate().is_ok());
```
### `petri` — Petri net / WF-net shape
Core types: `PetriNet`, `Place`, `Transition`, `Arc`. For typed WF-nets with
compile-time soundness claims see `nightly_foundry::petri_law::WfNetConst`.
### `powl` — Partially Ordered Workflow Language
Core types: `PowlNode`, `PowlOperator`, `PowlModel`. The `TreeProjectable` sealed
trait enforces that only lawful POWL node types can enter a tree projection.
### `process_tree` — process tree shape
Core types: `ProcessTreeNode`, `TreeOperator`. `TypedLoopNode<ARITY>` uses
`Require<{ ARITY == 2 }>: IsTrue` to enforce binary loops at compile time.
### `dfg` — Directly-Follows Graph
Core types: `DfgNode`, `DfgEdge`, `DfgWeight`, `Dfg`, `DfgRefusal`, `DfgEdgeFull`,
`ObjectCentricDfg`. The DFG is the minimal process evidence structure (van der Aalst):
nodes are activity classes, edges are directly-follows relations with observed counts.
`Dfg::validate()` checks for dangling edges and empty graphs. `ObjectCentricDfg` holds
one `Dfg` per object type for OCEL logs. Structure only — graduate to `wasm4pm` to mine.
Running example: `cargo run --example dfg_shape`
### `declare` — Declare constraint model
Core types: `DeclareConstraint`, `DeclareModel`, `ConstraintTemplate`.
See `witness::DeclareFamily` and `witness::DeclareConstraints` for the two
authority layers.
### `ocpq` — Object-Centric Process Querying
Core types: `OcpqQuery`, `OcpqPredicate`. See `witness::OcpqPaper`.
### `bpmn` — BPMN shape
Core types: `BpmnProcess`, `BpmnFlow`, `BpmnGateway`, `BpmnTask`.
### `conformance` — conformance verdict shape
Core types: `ConformanceVerdict`, `Metric<KIND, NUM, DEN>`.
`Metric<KIND, NUM, DEN>` uses `Between01<NUM, DEN>` from `law` to ensure the
metric value is provably in `[0, 1]` at the type level:
```rust
// This requires nightly feature flags in the caller.
// use wasm4pm_compat::conformance::Metric;
// use wasm4pm_compat::law::MetricKind;
// type Fitness = Metric<{ MetricKind::Fitness }, 9, 10>; // 0.9
```
### `prediction` — prediction problem shape
Core types: `PredictionProblem<T>`, `PredictionTarget`, `PredictionHorizon`, `PredictionRefusal`,
`ComplianceKind`, and six target witnesses (`NextActivity`, `OutcomeLabel`, `RemainingTime`,
`DriftSignal`, `RiskScore`, `ComplianceTarget`). Structure only; this module states the
prediction *problem*, never the *answer*. Graduate to `wasm4pm` to train, encode, and infer.
Running example: `cargo run --example prediction_problem_shape`
### `streaming` — streaming process evidence context
Core types: `StreamingSource<WINDOW_SIZE>`, `EventWindow<T, SIZE>`,
`OnlineMonitoringContext`, `OfflineAnalysisContext`, `ContextualEvidence<T, Context>`,
`OnlineEvidence<T>`, `OfflineEvidence<T>`, `TemporalOrderConfusion`.
`EventWindow<T, SIZE>` is a circular buffer (ring) — `push()` returns the evicted
slot when the window is full. `OnlineEvidence<T>` and `OfflineEvidence<T>` are
**different types**: the compiler prevents an online stream from silently substituting
for a completed log. Structure only — graduate to `wasm4pm` for windowed ingestion
and online conformance checking.
Running example: `cargo run --example streaming_context`
### `process_cube` — Process Cube framework shapes
Core types: `CubeDimension<NAME>`, `CubeDimensionKind`, `CubeSlice<D, V>`,
`CubeCell<DIMS>`, `CubeProjectionWitness<FROM, TO>`, `ProcessCube<Log, DIMS>`,
`CellComparison<DIMS>`.
`CubeDimension<"resource">` and `CubeDimension<"time">` are **distinct types** — axis
identity is enforced at compile time. `CubeProjectionWitness<FROM, TO>` records
the arity reduction from a projection step. The process cube framework (van der Aalst 2013)
enables multi-dimensional comparison: slicing by resource, time, activity, or OCEL object
type, then comparing sub-log behavior per cell. Structure only — graduate to `wasm4pm`
for sub-log extraction, per-cell discovery, and cross-cell conformance comparison.
Running example: `cargo run --example process_cube_shape`
### `multiperspective` — multi-perspective process evidence
Core types: `ProcessPerspective`, `ControlFlowPerspective`, `DataPerspective`,
`ResourcePerspective`, `TimePerspective`, `MultiPerspectiveEvidence<T, Perspectives>`,
`PerspectiveCombination<A, B>`, `ParityComparer`.
The four perspectives of van der Aalst's process mining framework: control-flow (what
and in what order), data (attributes and guards), resource (who performs what), time
(when, how long). Each perspective is a distinct zero-sized type, so substituting one
for another is a compile error. Combine with `PerspectiveCombination<A, B>`, nestable
for 3 or 4 perspectives. Structure only — graduate to `wasm4pm` for multi-perspective
alignment cost computation and balanced conformance checking (Mannhardt et al. 2016).
Running example: `cargo run --example multiperspective_evidence`
### `object_lifecycle` — object lifecycle phase shapes
Core types: `ObjectLifecyclePhase`, `LifecycledObject<T, PHASE>`, `ObjectState<PHASE>`,
`LifecycleTransition<FROM, TO>`, `ObjectLifecycleWitness`, type aliases
`CreatedObject<T>` / `ActiveObject<T>` / `ModifiedObject<T>` / `ArchivedObject<T>` /
`DeletedObject<T>`.
Const-generic typestate for OCEL object lifecycle phases. Each transition method
(`activate()`, `modify()`, `archive()`, `delete()`) changes the type; illegal
transitions are compile errors. **OPEN-doc-substrate:** a running example is pending
due to a nightly `E0391` const-generic cycle in `ConstParamTy` impl specialization.
The module is fully documented and the types are exercised by unit tests. Graduate to
`wasm4pm` for lifecycle discovery and conformance checking.
### `causal_net` — causal net shape
Core types: `CausalNet`, `CausalNode`, `InputBinding`, `OutputBinding`.
Covers Heuristics Miner output (Weijters & Ribeiro 2011).
---
## Cross-product composition example
After per-module coverage, the highest-value witness is an example that **composes
multiple modules end-to-end**. Per-module examples prove surface completeness;
cross-product examples prove coherence. A broken import, renamed type, or changed
method signature makes at least one assertion in the composition example fail.
The pipeline example threads 7 modules into a realistic process-intelligence story:
```text
OcelLog (ocel)
└─► ObjectCentricDfg (dfg)
└─► ConformanceTriple + check_filter_shape (interop)
└─► PredictionProblem<NextActivity> (prediction)
└─► MultiPerspectiveEvidence<ControlFlow+Time> (multiperspective)
└─► ProcessCube<OcelLog, 2> + CubeSlice (process_cube)
```
No module does computation — they hold shapes. The example witnesses that the shapes
fit together. Graduate to `wasm4pm` for: DFG mining, conformance replay/alignment,
prediction inference, multi-perspective cost weighting, cube sub-log extraction.
Running example: `cargo run --example process_pipeline_composition`