Metadata & Events
What actually gets published to the UI/logs at each lifecycle stage today — verified against the code, not assumed.
The Publishing Path
report (ExecutionProgressReport)
→ ReportExecutionProgressLifeCycleAsync(ctx, report)
→ ReportExecutionProgressAsync(ctx, report) // BaseNodeExecutor.ProgressReport.cs
├── mirror: ActivityPublisher.ReportExecutionProgressAsync(...) // best-effort, logged on failure
│ → converts to ExecutionActivityMilestone, routes to enabled targets
│ → milestone has NO Metadata property — report.Metadata is not carried into it
└── if report.RequiresEventLog:
sync report.Metadata → report.EventDescription.Metadata
→ Processor.RaiseNodeProgressEvent4Async(ctx, report.EventDescription, message)
← THIS is what reaches the frontend/SignalR
BaseNodeExecutor.Reporting.Legacy\BaseNodeExecutor.ProgressReport.cs carries a large comment block saying it's "DEPRECATED & CONVERTED TO ExecutionActivityPublisher" — but its ReportExecutionProgressAsync(NodeExecutionContext, ExecutionProgressReport) method is the one every stage report actually flows through, including the mirror call into the newer ExecutionActivityPublisher. The newer publisher does not yet replace it end-to-end; it supplements it.
What Each Stage Actually Carries Today
| Stage | Where built | Metadata attached |
|---|---|---|
Initiated | OnExecute (BaseNodeExecutor.Orchestrate.cs:95-98) | inputData — scrubbed InputData. The only stage carrying input today. |
Entry | line 100-101 | none |
EntryValidate | line 103-104 | none |
PreValidationGuardRails | line 106-107 | none |
PreProcess | line 112-113 | none |
Process | line 116-117 | none |
PostProcess | line 119-120 | none |
PostValidationGuardRails | line 122-123 | none |
Exit | line 128-129 | none |
Completed / Error | Execute() line 78/84, via CreateStageReportByResult | safe scalar summary only: OutputPortKey, IsSuccess, IsSkipped, IsRetry, DurationMs, ErrorMessage, ExceptionMessage — explicitly not raw OutputData/OutputBranches/Exception |
ExecutionProgressReportFactoryResultExtensions.cs:58-61 has this comment on the code that builds the Completed/Error metadata:
// Project a safe scalar summary instead of the raw NodeExecutionResult — the raw object
// graph (Exception, OutputPort, OutputBranches) is not guaranteed JSON-serializable and
// previously crashed SignalR's write pump (System.NotSupportedException on IntPtr/WaitHandle
// reachable through Exception), silently corrupting the client connection.
Any change that adds output data back into this stage's metadata must keep this constraint in mind — project a safe, scrubbed summary, never the raw NodeExecutionResult/Exception/OutputBranches graph.
Correcting a Common Assumption
It's easy to assume every stage broadcasts both input and output, given how many stages exist. In the current code, 8 of the 10 stages carry no data payload at all, and none currently carry full OutputData — only Initiated (input) and Completed/Error (a scalar result summary) carry anything.
If a UI or log is showing more than that, the most likely sources are outside this lifecycle pipeline:
- Individual node executors' own
LogActivity(...)calls — each executor decides its own payload, independent of the stage reports above. - UI panels (e.g. a Nodes tab's Input/Config/Output columns) that read directly from
NodeExecutionResultor pinned branches via a separate API, not through these events.
Recommended Shape (Not Yet Implemented)
Input at Entry, not Initiated
Move the inputData metadata attachment to fire on the Entry report instead of (or in addition to, during a transition) Initiated, so "input" and "the stage named Entry" mean the same thing to anyone reading the event stream.
Output at Completed
Add a scrubbed, safe projection of OutputData to the Completed stage's metadata — following the same JSON-serializability discipline already established for the scalar summary, not the raw dictionary.
Never publish OutputBranches' duplicated Data
If any future change is tempted to attach OutputBranches to an event for convenience, replace it with a path reference instead (e.g. nodeKey + field name) — the branch data is already retrievable on demand via memory.GetNodeResultBranches(nodeKey), so publishing the duplicated payload adds size and cycle risk (see Pinned Data) for no new information.