Exact comparisons
Use overlap, residual, missing, coverage, containment, or lead/lag rows when the result must explain precise agreement and disagreement between two selected histories.
Guide · C# and Rust
Ordered sequences answer whether one key, source, and partition lane passed through literal named window families in order. The matcher is deliberately bounded: a linear list of steps, optional per-transition gaps, deterministic evidence selection, and no branching, negation, loops, or causal claim.
Choose the analytical unit
A sequence step names a window family such as Warning,
Offline, or Recovered. It does not describe a
low-level event type or an arbitrary predicate over records. Record
the interpreted states first, then ask whether a lane followed the
named journey.
Use overlap, residual, missing, coverage, containment, or lead/lag rows when the result must explain precise agreement and disagreement between two selected histories.
Use Episodes when nearby fragments of the same interpreted condition should form occurrences that are then related across target and against sides.
Use a sequence when different named conditions form a meaningful order within one lane: warning, outage, recovery; queued, processing, completed; or suspected, confirmed, resolved.
These views can be used together. A completed incident sequence says the three states occurred in order; it does not say two providers agreed on their exact ranges, or combine those windows into one occurrence.
Pattern and lane
A plan has a non-blank analytical name, at least two literal window-family
names, and an optional inclusive maximum processing-position gap.
Repeating a family is valid: A → B → A requires separate
records because one source record cannot fill two steps.
1. Anchor
Only a record for the configured first step can anchor a completed match.
2. Correlate
Evidence never crosses a device, provider, stage, or partition boundary to complete a journey.
3. Advance
Later steps may overlap earlier evidence; onset order, rather than non-overlap, defines the sequence.
4. Complete
Only a complete chain becomes a match and consumes its contributing record IDs.
Rust lane identity uses exact string equality for key, source, and partition. In C#, the first step family's configured history key comparer anchors key equality for every later step; source and partition use exact object equality. This is a real native-model difference, so normalize key conventions before expecting cross-runtime agreement.
Deterministic matching
Within each lane, candidates for a step are ordered by effective end, then onset, then record ID. Starting from each first-step candidate, the matcher takes the first compatible unused candidate for every next step. It does not enumerate alternative chains or optimize for the largest number of possible matches.
| Rule | Consequence |
|---|---|
| Onset order | A candidate is eligible when its start is at or after the previous step's start. Overlapping steps are allowed. |
| Earliest completion | The lowest effective end wins first; onset and record ID resolve ties deterministically. |
| Commit on completion | An incomplete attempted chain consumes nothing, so its evidence may still support a later complete chain. |
| Non-reuse | After a complete match commits, none of its source record IDs can contribute to another match in that lane. |
| Stable result order | Completed matches are returned by end, then start, then contributing record IDs, with native lane identity used as an additional Rust tie-breaker. |
Optional gap constraint
For a transition, inactive gap is
max(0, next start − previous effective end). An overlap
therefore has gap zero. WithMaximumGap(...) /
with_maximum_gap(...) applies the inclusive limit to every
transition independently; it is not an end-to-end duration limit.
Any later-onset record in the same lane can complete the next step, regardless of intervening inactivity.
A transition with gap 5 qualifies; gap 6 does not. Each pair in a three-step sequence is checked separately.
The match sums the positive inactive gaps between selected steps. Its end-to-end magnitude is separately measured from the first onset to the latest effective end.
Direct library APIs
The examples assume history already contains recorded
Warning, Offline, and Recovered
windows. Historical execution requires processing-position evidence
and requires every record from the selected families in that history
to be closed.
using Spanfold.Sequences;
var result = history
.MatchSequence("incident journey")
.Step("Warning")
.Then("Offline")
.Then("Recovered")
.WithMaximumGap(5)
.Run();
foreach (var match in result.Matches)
{
Console.WriteLine(
$"{match.Key}: {match.StartPosition}..{match.EndPosition} " +
$"gap={match.TotalGapPositionMagnitude} {match.Finality}");
foreach (var record in match.Evidence)
{
Console.WriteLine($" {record.Window.WindowName} {record.Window.Id}");
}
}
let matches = history
.match_sequence("incident journey")
.step("Warning")
.then("Offline")
.then("Recovered")
.with_maximum_gap(5)
.run()?;
for sequence_match in &matches {
println!(
"{}: {}..{} gap={} {:?}",
sequence_match.key(),
sequence_match.start().magnitude(),
sequence_match.end().magnitude(),
sequence_match.total_gap(),
sequence_match.finality(),
);
for record in sequence_match.evidence() {
println!(" {} {}", record.window.window_name(), record.window.id());
}
}
Builder validation follows each host language: C# rejects invalid calls
as the fluent plan is built or run; Rust returns
WindowSequenceError from execution.
Lineage and output
Evidence is an ordered snapshot record per configured step. Each
snapshot record retains the source WindowRecord and its
record ID, plus the effective range and finality used by matching.
Keep this native evidence when an audit must explain why a journey was
accepted.
| Output | C# / .NET source | Rust source |
|---|---|---|
| Execution result | WindowSequenceResult with Plan, ordered Matches, and optional EvaluationHorizon. | Vec<WindowSequenceMatch>; there is no enclosing result object or stored evaluation horizon. |
| Plan name | Available through result.Plan.Name. | Stored on every match and returned by name(). |
| Lane identity | Key, Source, and Partition are native objects. | key() is a string; source() and partition() are optional strings. |
| Range and magnitude | StartPosition, EndPosition, EndToEndPositionMagnitude, and TotalGapPositionMagnitude. | start(), end(), end_to_end_magnitude(), and total_gap(). |
| Lineage | Evidence contains ordered WindowSnapshotRecord values with source window IDs. | evidence() returns the same native snapshot-record shape. |
| Portable output | There is currently no portable sequence plan/result document, deterministic cross-runtime sequence schema, or CLI sequence route. | |
Live horizon and finality
Use RunLive(TemporalPoint.ForPosition(horizon)) or
run_live(TemporalPoint::position(horizon)). The history
snapshot excludes future evidence, clips a still-open or not-yet-ended
record to the horizon, and marks that snapshot record provisional.
using Spanfold.Comparison;
var live = history
.MatchSequence("incident journey")
.Step("Warning")
.Then("Offline")
.Then("Recovered")
.WithMaximumGap(5)
.RunLive(TemporalPoint.ForPosition(42));
var provisional = live.Matches
.Where(match => match.Finality == ComparisonFinality.Provisional);
let live = history
.match_sequence("incident journey")
.step("Warning")
.then("Offline")
.then("Recovered")
.with_maximum_gap(5)
.run_live(TemporalPoint::position(42))?;
let provisional = live.iter().filter(|sequence_match| {
sequence_match.finality() == &ComparisonFinality::Provisional
});
A completed match is provisional exactly when any selected snapshot record is provisional. If every selected record is final, the match is final.
The maximum gap only tests transitions that were selected. It does not keep an otherwise final match provisional while waiting for another possible future candidate.
A live run returns completed visible chains. It does not emit a provisional “not matched yet” row for a journey that future evidence might complete.
The horizon does not imply a watermark, allowed lateness, retention policy, or source completeness. Callers own those guarantees and rerun matching when history changes.
Availability
Ordered sequences landed in the repository after the current published NuGet and crates.io baselines. Use a repository checkout until each ecosystem receives a later release that explicitly includes the feature.
| Runtime | Current repository source | Published package |
|---|---|---|
| C# / .NET | Implemented in source version 0.2.0-preview.1 under Spanfold.Sequences. | NuGet.org Spanfold 0.1.0-preview.2 predates ordered sequences. |
| Rust | Implemented on the current branch under spanfold::sequences; the crate manifest still reads 0.1.1. | The published crates.io spanfold 0.1.1 release predates ordered sequences despite sharing the source tree's current version number. |
The native semantics intentionally correspond, but package versioning, result wrapping, key identity, and error handling remain runtime-specific. No portable document currently turns the sequence surface into a cross-runtime compatibility contract.
Next
Read exact comparison semantics when range disagreement matters, use Episode analysis when fragments should form occurrences, or define live ordering and completeness ownership before publishing changing sequence results.