* feat(bpmn): project interpreter diagnostics onto the scope's execution log Under Option A only bound work carries an activity id, so gateways, events and flows had no per-element trace in the journal. BpmnScopeHost now projects each new BpmnExecutionState.Diagnostics entry onto the scope's own execution log before Prune() runs, keyed by element id, with a persisted high-water mark so a resumed scope never re-emits one. The scope's own start and completion stay out, since they are already journaled as the activity's own lifecycle. Event names and the payload shape are documented as a public compatibility surface for elsa-studio#1000 to mirror. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(bpmn): project element and flow diagnostics dropped by the FlowId exclusion The diagnostics exclusion keyed on Kind == TokenEmitted && FlowId is null/empty also dropped an error or cancel boundary's own token emission, since a boundary fires without an inbound flow. Narrow the rule to skip only diagnostics that name neither an element nor a flow -- the scope's own terminal Completed summary -- so every diagnostic keyed on an element or a flow, including a start event's and a boundary's, is projected. Also make DiagnosticSequence resilient: TryParse instead of Parse, logging a warning and skipping projection for an id that doesn't match diag:N rather than faulting the evaluation. Add a reflection-based test that keeps BpmnDiagnosticEventNames in lockstep with BpmnDiagnosticKind, and record the diagnostics volume measurement in the wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(bpmn): require exact diag:N ids and seed the diagnostics cursor from prior state Reject any diagnostic id that is not the exact "diag:" prefix followed by a non-negative integer, so a malformed id can no longer poison the durable projection cursor and cause later, genuinely valid, lower-sequence diagnostics to be skipped forever. Also seed a missing cursor from the highest valid sequence in the scope's prior persisted state instead of treating it as zero, so a scope persisted before diagnostics projection existed does not replay every retained historical diagnostic as new on its next evaluation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.6 KiB
C#
40 lines
1.6 KiB
C#
using System.Reflection;
|
|
using Bpmn.Model.State;
|
|
using Elsa.Bpmn.Hosting;
|
|
|
|
namespace Elsa.Bpmn.UnitTests;
|
|
|
|
/// <summary>
|
|
/// <see cref="BpmnDiagnosticEventNames"/> mirrors every member of <see cref="BpmnDiagnosticKind"/> by name so
|
|
/// <see cref="BpmnScopeHost"/> can derive an execution log event name from the enum without depending on the
|
|
/// library's integer values. Nothing in the compiler links the two, so a library upgrade that renames or adds a
|
|
/// member would drift silently; these tests catch that at build time instead.
|
|
/// </summary>
|
|
public class BpmnDiagnosticEventNamesTests
|
|
{
|
|
private static readonly IReadOnlyDictionary<string, string> EventNameConstants = typeof(BpmnDiagnosticEventNames)
|
|
.GetFields(BindingFlags.Public | BindingFlags.Static)
|
|
.Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string) && f.Name != nameof(BpmnDiagnosticEventNames.Source))
|
|
.ToDictionary(f => f.Name, f => (string)f.GetRawConstantValue()!);
|
|
|
|
[Fact]
|
|
public void EveryDiagnosticKindMember_HasAnIdenticallyNamedConstant()
|
|
{
|
|
var memberNames = Enum.GetNames<BpmnDiagnosticKind>();
|
|
|
|
Assert.All(memberNames, name =>
|
|
{
|
|
Assert.True(EventNameConstants.TryGetValue(name, out var value), $"'{name}' has no matching constant in {nameof(BpmnDiagnosticEventNames)}.");
|
|
Assert.Equal(name, value);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void NoConstant_IsNotADiagnosticKindMember()
|
|
{
|
|
var memberNames = Enum.GetNames<BpmnDiagnosticKind>().ToHashSet();
|
|
|
|
Assert.All(EventNameConstants.Keys, name => Assert.Contains(name, memberNames));
|
|
}
|
|
}
|