|
|
|
@@ -19,21 +19,21 @@ using ZB.MOM.WW.ScadaBridge.StoreAndForward;
|
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-18: Script Runtime API — injected into Script/Alarm Execution Actors.
|
|
|
|
|
/// Script Runtime API — injected into Script/Alarm Execution Actors.
|
|
|
|
|
/// Provides the API surface that user scripts interact with:
|
|
|
|
|
/// Instance.GetAttribute("name")
|
|
|
|
|
/// Instance.SetAttribute("name", value)
|
|
|
|
|
/// Instance.CallScript("scriptName", params)
|
|
|
|
|
/// Scripts.CallShared("scriptName", params)
|
|
|
|
|
///
|
|
|
|
|
/// WP-13 (Phase 7): Integration surface APIs:
|
|
|
|
|
/// (Phase 7): Integration surface APIs:
|
|
|
|
|
/// ExternalSystem.Call("systemName", "methodName", params)
|
|
|
|
|
/// ExternalSystem.CachedCall("systemName", "methodName", params)
|
|
|
|
|
/// Database.Connection("name")
|
|
|
|
|
/// Database.CachedWrite("name", "sql", params)
|
|
|
|
|
/// Notify.To("listName").Send("subject", "message")
|
|
|
|
|
///
|
|
|
|
|
/// WP-20: Recursion Limit — call depth tracked and enforced.
|
|
|
|
|
/// Recursion Limit — call depth tracked and enforced.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class ScriptRuntimeContext
|
|
|
|
|
{
|
|
|
|
@@ -57,12 +57,12 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly CancellationToken _scriptTimeoutToken;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: External system client for ExternalSystem.Call/CachedCall.
|
|
|
|
|
/// External system client for ExternalSystem.Call/CachedCall.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly IExternalSystemClient? _externalSystemClient;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: Database gateway for Database.Connection/CachedWrite.
|
|
|
|
|
/// Database gateway for Database.Connection/CachedWrite.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly IDatabaseGateway? _databaseGateway;
|
|
|
|
|
|
|
|
|
@@ -84,7 +84,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly string _siteId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SourceNode-stamping (Task 13/14): the cluster node name supplied by
|
|
|
|
|
/// SourceNode-stamping: the cluster node name supplied by
|
|
|
|
|
/// <c>INodeIdentityProvider</c> on the local host — <c>node-a</c>/<c>node-b</c>
|
|
|
|
|
/// for site nodes. Stamped onto <c>NotificationSubmit.SourceNode</c> by
|
|
|
|
|
/// <see cref="NotifyTarget.Send"/> and onto <c>SiteCallOperational.SourceNode</c>
|
|
|
|
@@ -106,21 +106,21 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly string? _sourceScript;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// M2.12 (#25): site event logger for recording recursion-limit violations
|
|
|
|
|
/// Site event logger for recording recursion-limit violations
|
|
|
|
|
/// to the local SQLite event log. Optional — when null the emission is
|
|
|
|
|
/// skipped; the existing <c>_logger.LogError</c> + throw path is unchanged.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly ISiteEventLogger? _siteEventLogger;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23: best-effort emitter for boundary-crossing actions executed
|
|
|
|
|
/// Best-effort emitter for boundary-crossing actions executed
|
|
|
|
|
/// by the script. Optional — when null the helpers degrade to a no-op audit
|
|
|
|
|
/// path so tests / contexts that do not need the audit pipeline still work.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly IAuditWriter? _auditWriter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M3): site-local tracking store consulted by
|
|
|
|
|
/// Site-local tracking store consulted by
|
|
|
|
|
/// <c>Tracking.Status(TrackedOperationId)</c>. Optional — when null the
|
|
|
|
|
/// helper throws on access, mirroring the existing
|
|
|
|
|
/// "service-not-wired" behaviour of the other integration helpers.
|
|
|
|
@@ -128,7 +128,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly IOperationTrackingStore? _operationTrackingStore;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M3 Bundle E — Task E3): site-side dual emitter for
|
|
|
|
|
/// Site-side dual emitter for
|
|
|
|
|
/// cached-call lifecycle telemetry. Optional — when null
|
|
|
|
|
/// <c>ExternalSystem.CachedCall</c> / <c>Database.CachedWrite</c> still
|
|
|
|
|
/// return a <see cref="TrackedOperationId"/> and invoke the underlying
|
|
|
|
@@ -138,7 +138,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly ICachedCallTelemetryForwarder? _cachedForwarder;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23: the per-execution id for this script run. Every
|
|
|
|
|
/// The per-execution id for this script run. Every
|
|
|
|
|
/// trust-boundary audit row emitted by this script execution
|
|
|
|
|
/// (sync <c>ApiCall</c>/<c>DbWrite</c>, cached-call lifecycle rows,
|
|
|
|
|
/// <c>NotifySend</c>) is stamped into <c>AuditEvent.ExecutionId</c> with
|
|
|
|
@@ -149,15 +149,14 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly Guid _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's
|
|
|
|
|
/// <see cref="_executionId"/> when this script run was spawned by another
|
|
|
|
|
/// execution — for an inbound-API-routed call this is the inbound request's
|
|
|
|
|
/// per-request execution id. <c>null</c> for normal (tag-change /
|
|
|
|
|
/// timer-triggered) runs and nested <c>CallScript</c> invocations. The
|
|
|
|
|
/// routed script still mints its OWN fresh <see cref="_executionId"/>; this
|
|
|
|
|
/// field records the spawner so a spawned execution's audit rows can point
|
|
|
|
|
/// back at the execution that spawned it. (Task 5 wires the emitter that
|
|
|
|
|
/// stamps this onto <c>AuditEvent.ParentExecutionId</c>.)
|
|
|
|
|
/// back at the execution that spawned it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid? _parentExecutionId;
|
|
|
|
|
|
|
|
|
@@ -183,14 +182,14 @@ public class ScriptRuntimeContext
|
|
|
|
|
/// <param name="operationTrackingStore">Optional store for tracking cached operation status.</param>
|
|
|
|
|
/// <param name="cachedForwarder">Optional forwarder for cached call telemetry.</param>
|
|
|
|
|
/// <param name="executionId">
|
|
|
|
|
/// Audit Log #23: the per-execution id for this script run. When omitted
|
|
|
|
|
/// The per-execution id for this script run. When omitted
|
|
|
|
|
/// (tag-change / timer-triggered executions) a fresh id is generated; an
|
|
|
|
|
/// inbound caller may supply one to tie the execution to an upstream
|
|
|
|
|
/// request. Stamped into <c>AuditEvent.ExecutionId</c> on every
|
|
|
|
|
/// trust-boundary audit row this execution emits.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <param name="parentExecutionId">
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's
|
|
|
|
|
/// <c>ExecutionId</c> — supplied for an inbound-API-routed call (the
|
|
|
|
|
/// inbound request's per-request id), <c>null</c> for normal (tag-change /
|
|
|
|
|
/// timer-triggered) runs. The routed script still generates its own fresh
|
|
|
|
@@ -198,7 +197,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <param name="sourceNode">Optional cluster node identifier (node-a/node-b) for audit trail stamping.</param>
|
|
|
|
|
/// <param name="siteEventLogger">
|
|
|
|
|
/// M2.12 (#25): optional site event logger. When supplied, recursion-limit
|
|
|
|
|
/// Optional site event logger. When supplied, recursion-limit
|
|
|
|
|
/// violations at <c>CallScript</c> and <c>CallShared</c> emit a
|
|
|
|
|
/// <c>script</c> Error event in addition to the existing
|
|
|
|
|
/// <c>ILogger.LogError</c> + throw. When null the existing behaviour is
|
|
|
|
@@ -252,16 +251,16 @@ public class ScriptRuntimeContext
|
|
|
|
|
_auditWriter = auditWriter;
|
|
|
|
|
_operationTrackingStore = operationTrackingStore;
|
|
|
|
|
_cachedForwarder = cachedForwarder;
|
|
|
|
|
// SourceNode-stamping (Task 13/14): the local node name read from
|
|
|
|
|
// SourceNode-stamping: the local node name read from
|
|
|
|
|
// INodeIdentityProvider at the ScriptExecutionActor; null when no
|
|
|
|
|
// provider was wired so the downstream callsites pass null through
|
|
|
|
|
// verbatim — leaving central SourceNode as NULL.
|
|
|
|
|
_sourceNode = sourceNode;
|
|
|
|
|
_executionId = executionId ?? Guid.NewGuid();
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): stored verbatim — no `?? NewGuid()`
|
|
|
|
|
// (ParentExecutionId): stored verbatim — no `?? NewGuid()`
|
|
|
|
|
// fallback. A non-routed run legitimately has no parent and stays null.
|
|
|
|
|
_parentExecutionId = parentExecutionId;
|
|
|
|
|
// M2.12 (#25): optional — null when not wired (tests / AlarmExecutionActor).
|
|
|
|
|
// Optional — null when not wired (tests / AlarmExecutionActor).
|
|
|
|
|
_siteEventLogger = siteEventLogger;
|
|
|
|
|
// WaitForAttribute (spec §4.3): default(CancellationToken) == None when
|
|
|
|
|
// not threaded in — the WaitAsync Ask is then bounded only by its own timeout.
|
|
|
|
@@ -269,20 +268,20 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M5.4): this run's own per-execution id. Exposed so a
|
|
|
|
|
/// This run's own per-execution id. Exposed so a
|
|
|
|
|
/// nested <c>Scripts.CallShared</c> can record it as the spawned shared
|
|
|
|
|
/// script's <c>ParentExecutionId</c>, forming a true execution tree.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Guid ExecutionId => _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M5.4): the spawning execution's id for this run (null for
|
|
|
|
|
/// The spawning execution's id for this run (null for
|
|
|
|
|
/// a root run). Exposed for test assertions on the execution tree.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Guid? ParentExecutionId => _parentExecutionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M5.4 — ParentExecutionId tag-cascade): builds a child
|
|
|
|
|
/// (ParentExecutionId tag-cascade): builds a child
|
|
|
|
|
/// <see cref="ScriptRuntimeContext"/> for an inline <c>Scripts.CallShared</c>
|
|
|
|
|
/// invocation. The shared script runs inline (no actor hop) but is modelled
|
|
|
|
|
/// as its OWN execution node in the audit tree: it mints a fresh
|
|
|
|
@@ -326,9 +325,9 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// M2.12 (#25): fire-and-forget emission of a <c>script</c> Error site event
|
|
|
|
|
/// Fire-and-forget emission of a <c>script</c> Error site event
|
|
|
|
|
/// for a recursion-limit violation. Mirrors the call shape used by
|
|
|
|
|
/// <c>ScriptExecutionActor</c>'s catch blocks (WP-32 / M1.8). A fault from
|
|
|
|
|
/// <c>ScriptExecutionActor</c>'s catch blocks. A fault from
|
|
|
|
|
/// the site-event logger is observed-and-dropped (best-effort) via
|
|
|
|
|
/// <c>ContinueWith(OnlyOnFaulted)</c> — it never blocks or faults the
|
|
|
|
|
/// <c>_logger.LogError</c> + throw path that follows. A null logger is a no-op.
|
|
|
|
@@ -565,8 +564,8 @@ public class ScriptRuntimeContext
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calls a sibling script on the same instance by name (Ask pattern).
|
|
|
|
|
/// WP-20: Enforces recursion limit.
|
|
|
|
|
/// WP-22: Uses Ask pattern for CallScript.
|
|
|
|
|
/// Enforces recursion limit.
|
|
|
|
|
/// Uses Ask pattern for CallScript.
|
|
|
|
|
/// <paramref name="parameters"/> may be a dictionary or an anonymous object
|
|
|
|
|
/// (<c>new { name = "Bob" }</c>) — see <see cref="ScriptArgs"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
@@ -581,7 +580,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
var msg = $"Script call depth exceeded maximum of {_maxCallDepth}. " +
|
|
|
|
|
$"CallScript('{scriptName}') rejected at depth {nextDepth}.";
|
|
|
|
|
_logger.LogError(msg);
|
|
|
|
|
// M2.12 (#25): emit to site event log in addition to ILogger; fire-and-forget.
|
|
|
|
|
// Emit to site event log in addition to ILogger; fire-and-forget.
|
|
|
|
|
EmitRecursionLimitEventAsync(msg);
|
|
|
|
|
throw new InvalidOperationException(msg);
|
|
|
|
|
}
|
|
|
|
@@ -592,7 +591,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
ScriptArgs.Normalize(parameters),
|
|
|
|
|
nextDepth,
|
|
|
|
|
correlationId,
|
|
|
|
|
// Audit Log #23 (M5.4 — ParentExecutionId tag-cascade): the child
|
|
|
|
|
// (ParentExecutionId tag-cascade): the child
|
|
|
|
|
// script run is a NEW execution spawned BY this run. Its parent is
|
|
|
|
|
// THIS run's own ExecutionId — NOT the inherited _parentExecutionId.
|
|
|
|
|
// So A → CallScript(B) yields B.ParentExecutionId == A.ExecutionId,
|
|
|
|
@@ -618,25 +617,25 @@ public class ScriptRuntimeContext
|
|
|
|
|
public ScriptCallHelper Scripts => new(_sharedScriptLibrary, this, _currentCallDepth, _maxCallDepth, _logger);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: Provides access to external system calls.
|
|
|
|
|
/// Provides access to external system calls.
|
|
|
|
|
/// ExternalSystem.Call("systemName", "methodName", params)
|
|
|
|
|
/// ExternalSystem.CachedCall("systemName", "methodName", params)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public ExternalSystemHelper ExternalSystem => new(
|
|
|
|
|
_externalSystemClient, _instanceName, _logger, _executionId, _auditWriter, _siteId, _sourceScript,
|
|
|
|
|
// Audit Log #23 (M3 Bundle E — Task E3): emit CachedSubmit telemetry
|
|
|
|
|
// Emit CachedSubmit telemetry
|
|
|
|
|
// on every ExternalSystem.CachedCall enqueue.
|
|
|
|
|
_cachedForwarder,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// threaded alongside _executionId. Null for non-routed runs.
|
|
|
|
|
_parentExecutionId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name (node-a/node-b),
|
|
|
|
|
// SourceNode-stamping: the local node name (node-a/node-b),
|
|
|
|
|
// threaded so the cached-call telemetry construction sites can stamp
|
|
|
|
|
// it onto SiteCallOperational.SourceNode.
|
|
|
|
|
_sourceNode);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: Provides access to database operations.
|
|
|
|
|
/// Provides access to database operations.
|
|
|
|
|
/// Database.Connection("name")
|
|
|
|
|
/// Database.CachedWrite("name", "sql", params)
|
|
|
|
|
/// </summary>
|
|
|
|
@@ -645,20 +644,20 @@ public class ScriptRuntimeContext
|
|
|
|
|
_instanceName,
|
|
|
|
|
_logger,
|
|
|
|
|
_executionId,
|
|
|
|
|
// Audit Log #23 (M4 Bundle A): wire the IAuditWriter so
|
|
|
|
|
// Wire the IAuditWriter so
|
|
|
|
|
// Database.Connection(name) returns an auditing decorator that
|
|
|
|
|
// emits one DbOutbound/DbWrite row per script-initiated
|
|
|
|
|
// Execute / ExecuteScalar / ExecuteReader.
|
|
|
|
|
_auditWriter,
|
|
|
|
|
_siteId,
|
|
|
|
|
_sourceScript,
|
|
|
|
|
// Audit Log #23 (M3 Bundle E — Task E6): emit CachedSubmit telemetry on
|
|
|
|
|
// Emit CachedSubmit telemetry on
|
|
|
|
|
// every Database.CachedWrite enqueue.
|
|
|
|
|
_cachedForwarder,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// threaded alongside _executionId. Null for non-routed runs.
|
|
|
|
|
_parentExecutionId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name (node-a/node-b),
|
|
|
|
|
// SourceNode-stamping: the local node name (node-a/node-b),
|
|
|
|
|
// threaded so Database.CachedWrite's CachedSubmit telemetry can
|
|
|
|
|
// stamp it onto SiteCallOperational.SourceNode.
|
|
|
|
|
_sourceNode);
|
|
|
|
@@ -670,7 +669,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
/// <c>Notify.Status(id)</c> queries the delivery status of that notification.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Audit Log #23 (M4 Bundle C): the <see cref="IAuditWriter"/> is threaded
|
|
|
|
|
/// The <see cref="IAuditWriter"/> is threaded
|
|
|
|
|
/// through so <c>Notify.To(list).Send(...)</c> emits one
|
|
|
|
|
/// <c>Notification</c>/<c>NotifySend</c> audit row per accepted submission.
|
|
|
|
|
/// Best-effort per alog.md §7 — a thrown writer never aborts the script's
|
|
|
|
@@ -679,15 +678,15 @@ public class ScriptRuntimeContext
|
|
|
|
|
public NotifyHelper Notify => new(
|
|
|
|
|
_storeAndForward, _siteCommunicationActor, _siteId, _instanceName, _sourceScript, _askTimeout, _logger,
|
|
|
|
|
_executionId, _auditWriter,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// (ParentExecutionId): the spawning execution's id,
|
|
|
|
|
// threaded alongside _executionId. Null for non-routed runs.
|
|
|
|
|
_parentExecutionId,
|
|
|
|
|
// SourceNode-stamping (Task 13): the local node name (node-a/node-b),
|
|
|
|
|
// SourceNode-stamping: the local node name (node-a/node-b),
|
|
|
|
|
// threaded so NotifyTarget.Send can stamp it onto NotificationSubmit.
|
|
|
|
|
_sourceNode);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M3): site-local tracking-status API for cached operations.
|
|
|
|
|
/// Site-local tracking-status API for cached operations.
|
|
|
|
|
/// <c>Tracking.Status(trackedOperationId)</c> reads the site SQLite tracking row
|
|
|
|
|
/// directly (authoritative source of truth — no central round-trip) and
|
|
|
|
|
/// returns a <see cref="TrackingStatusSnapshot"/>, or <c>null</c> when the
|
|
|
|
@@ -732,8 +731,8 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-17: Executes a shared script inline (direct method call, not actor message).
|
|
|
|
|
/// WP-20: Enforces recursion limit.
|
|
|
|
|
/// Executes a shared script inline (direct method call, not actor message).
|
|
|
|
|
/// Enforces recursion limit.
|
|
|
|
|
/// <paramref name="parameters"/> may be a dictionary or an anonymous
|
|
|
|
|
/// object (<c>new { name = "Bob" }</c>) — see <see cref="ScriptArgs"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
@@ -752,13 +751,13 @@ public class ScriptRuntimeContext
|
|
|
|
|
var msg = $"Script call depth exceeded maximum of {_maxCallDepth}. " +
|
|
|
|
|
$"CallShared('{scriptName}') rejected at depth {nextDepth}.";
|
|
|
|
|
_logger.LogError(msg);
|
|
|
|
|
// M2.12 (#25): emit to site event log via the parent context's
|
|
|
|
|
// Emit to site event log via the parent context's
|
|
|
|
|
// helper — single emission path, fire-and-forget.
|
|
|
|
|
_context.EmitRecursionLimitEventAsync(msg);
|
|
|
|
|
throw new InvalidOperationException(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Audit Log #23 (M5.4 — ParentExecutionId tag-cascade): the shared
|
|
|
|
|
// (ParentExecutionId tag-cascade): the shared
|
|
|
|
|
// script runs inline, but is modelled as its OWN execution node — a
|
|
|
|
|
// child context mints a fresh ExecutionId parented to the caller's
|
|
|
|
|
// ExecutionId, so its audit rows chain under the calling run.
|
|
|
|
@@ -770,10 +769,10 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: Helper for ExternalSystem.Call/CachedCall syntax.
|
|
|
|
|
/// Helper for ExternalSystem.Call/CachedCall syntax.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Audit Log #23 (M2 Bundle F): every <see cref="Call"/> invocation emits
|
|
|
|
|
/// Every <see cref="Call"/> invocation emits
|
|
|
|
|
/// one <c>ApiOutbound</c>/<c>ApiCall</c> audit row via <see cref="IAuditWriter"/>.
|
|
|
|
|
/// The audit emission is wrapped in a try/catch that swallows every exception
|
|
|
|
|
/// — the audit pipeline is best-effort and must NEVER abort the script's
|
|
|
|
@@ -795,10 +794,9 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly Guid _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// this run was inbound-API-routed; <c>null</c> for non-routed runs.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> ready for the Task 5
|
|
|
|
|
/// emitter — no audit row carries it yet.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> for audit-row stamping.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid? _parentExecutionId;
|
|
|
|
|
|
|
|
|
@@ -808,7 +806,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly ICachedCallTelemetryForwarder? _cachedForwarder;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SourceNode-stamping (Task 14): the local cluster node name on
|
|
|
|
|
/// SourceNode-stamping: the local cluster node name on
|
|
|
|
|
/// which this script is executing (<c>node-a</c>/<c>node-b</c>).
|
|
|
|
|
/// Stamped onto <c>SiteCallOperational.SourceNode</c> on the three
|
|
|
|
|
/// cached-call telemetry construction sites (CachedSubmit + the two
|
|
|
|
@@ -883,7 +881,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
if (_client == null)
|
|
|
|
|
throw new InvalidOperationException("External system client not available");
|
|
|
|
|
|
|
|
|
|
// Audit Log #23 (M2 Bundle F): wrap the outbound call so every
|
|
|
|
|
// Wrap the outbound call so every
|
|
|
|
|
// attempt emits exactly one ApiOutbound/ApiCall row. The wrapper
|
|
|
|
|
// mirrors the existing call-site behaviour — the original result
|
|
|
|
|
// OR original exception flows back to the script untouched; the
|
|
|
|
@@ -911,11 +909,11 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Submit a cached outbound API call (Audit Log #23 / M3). Mints a
|
|
|
|
|
/// Submit a cached outbound API call. Mints a
|
|
|
|
|
/// fresh <see cref="TrackedOperationId"/>, emits the lifecycle's first
|
|
|
|
|
/// <c>CachedSubmit</c> telemetry packet, hands the call to the
|
|
|
|
|
/// store-and-forward retry loop (which emits per-attempt and terminal
|
|
|
|
|
/// telemetry under the same id — Bundle E Tasks E4/E5), and returns
|
|
|
|
|
/// telemetry under the same id), and returns
|
|
|
|
|
/// the id immediately so the script can later query
|
|
|
|
|
/// <c>Tracking.Status(id)</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
@@ -953,15 +951,15 @@ public class ScriptRuntimeContext
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// Hand off to the existing cached-call path. The TrackedOperationId
|
|
|
|
|
// becomes the S&F message id so the retry loop (Bundle E Tasks
|
|
|
|
|
// E4/E5) can read it back via StoreAndForwardMessage.Id.
|
|
|
|
|
// becomes the S&F message id so the retry loop can read it back
|
|
|
|
|
// via StoreAndForwardMessage.Id.
|
|
|
|
|
//
|
|
|
|
|
// M3 Bundle F (F2): the result is now retained because the
|
|
|
|
|
// The result is now retained because the
|
|
|
|
|
// immediate-success path (WasBuffered=false) bypasses S&F entirely
|
|
|
|
|
// — no retry loop, no ICachedCallLifecycleObserver fire. The
|
|
|
|
|
// helper must emit the Attempted + CachedResolve terminal rows
|
|
|
|
|
// itself, otherwise Tracking.Status(id) would stay in Submitted
|
|
|
|
|
// forever and the audit log would be missing the M3 lifecycle.
|
|
|
|
|
// forever and the audit log would be missing the lifecycle.
|
|
|
|
|
// The WasBuffered=true path is unaffected — the S&F retry loop
|
|
|
|
|
// owns the Attempted + Resolve emissions in that case.
|
|
|
|
|
ExternalCallResult? result;
|
|
|
|
@@ -974,12 +972,12 @@ public class ScriptRuntimeContext
|
|
|
|
|
_instanceName,
|
|
|
|
|
cancellationToken,
|
|
|
|
|
trackedId,
|
|
|
|
|
// Audit Log #23 (ExecutionId Task 4): thread the script
|
|
|
|
|
// (ExecutionId): thread the script
|
|
|
|
|
// execution's ExecutionId + SourceScript so a buffered
|
|
|
|
|
// cached call's retry-loop audit rows carry them.
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
sourceScript: _sourceScript,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId Task 6): thread the
|
|
|
|
|
// (ParentExecutionId): thread the
|
|
|
|
|
// spawning inbound-API request's ExecutionId so a buffered
|
|
|
|
|
// cached call's retry-loop audit rows carry it too. Null
|
|
|
|
|
// for a non-routed run.
|
|
|
|
@@ -998,7 +996,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// M3 Bundle F (F2): immediate-completion lifecycle — emit the
|
|
|
|
|
// Immediate-completion lifecycle — emit the
|
|
|
|
|
// missing Attempted + CachedResolve rows when the underlying call
|
|
|
|
|
// resolved without engaging the store-and-forward retry loop.
|
|
|
|
|
if (result is { WasBuffered: false })
|
|
|
|
@@ -1045,7 +1043,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// per-execution id shared across this script run.
|
|
|
|
|
correlationId: trackedId.Value,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning
|
|
|
|
|
// (ParentExecutionId): the spawning
|
|
|
|
|
// execution's id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -1058,7 +1056,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
Channel: "ApiOutbound",
|
|
|
|
|
Target: target,
|
|
|
|
|
SourceSite: _siteId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name
|
|
|
|
|
// SourceNode-stamping: the local node name
|
|
|
|
|
// (node-a/node-b) — threaded through INodeIdentityProvider
|
|
|
|
|
// at the ScriptExecutionActor; null when no provider was
|
|
|
|
|
// wired so central persists SiteCalls.SourceNode as NULL.
|
|
|
|
@@ -1093,11 +1091,11 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// M3 Bundle F (F2): emit the Attempted + CachedResolve lifecycle
|
|
|
|
|
/// Emit the Attempted + CachedResolve lifecycle
|
|
|
|
|
/// rows for an immediate-completion <c>CachedCall</c> (WasBuffered=false).
|
|
|
|
|
/// The S&F retry loop never engaged, so the
|
|
|
|
|
/// <c>ICachedCallLifecycleObserver</c> never fires — the helper must
|
|
|
|
|
/// produce both rows itself to keep the M3 audit contract whole
|
|
|
|
|
/// produce both rows itself to keep the audit contract whole
|
|
|
|
|
/// (Submit → Attempted → Resolve under one TrackedOperationId).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
@@ -1162,7 +1160,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// ExecutionId = per-execution id for this script run.
|
|
|
|
|
correlationId: trackedId.Value,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning
|
|
|
|
|
// (ParentExecutionId): the spawning
|
|
|
|
|
// execution's id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -1177,7 +1175,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
Channel: "ApiOutbound",
|
|
|
|
|
Target: target,
|
|
|
|
|
SourceSite: _siteId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name
|
|
|
|
|
// SourceNode-stamping: the local node name
|
|
|
|
|
// (node-a/node-b) — threaded through INodeIdentityProvider
|
|
|
|
|
// at the ScriptExecutionActor; null when no provider was
|
|
|
|
|
// wired so central persists SiteCalls.SourceNode as NULL.
|
|
|
|
@@ -1230,7 +1228,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// ExecutionId = per-execution id for this script run.
|
|
|
|
|
correlationId: trackedId.Value,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning
|
|
|
|
|
// (ParentExecutionId): the spawning
|
|
|
|
|
// execution's id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -1245,7 +1243,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
Channel: "ApiOutbound",
|
|
|
|
|
Target: target,
|
|
|
|
|
SourceSite: _siteId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name
|
|
|
|
|
// SourceNode-stamping: the local node name
|
|
|
|
|
// (node-a/node-b) — threaded through INodeIdentityProvider
|
|
|
|
|
// at the ScriptExecutionActor; null when no provider was
|
|
|
|
|
// wired so central persists SiteCalls.SourceNode as NULL.
|
|
|
|
@@ -1367,7 +1365,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// Status: Delivered on a Success result; Failed otherwise (the
|
|
|
|
|
// ExternalSystemClient already maps HTTP non-2xx + transient
|
|
|
|
|
// exceptions into Success=false on the result, or surfaces a raw
|
|
|
|
|
// exception). M2 makes no distinction between transient + permanent
|
|
|
|
|
// exception). No distinction is made between transient + permanent
|
|
|
|
|
// failure here — both manifest as Status.Failed on the sync path.
|
|
|
|
|
var status = (thrown == null && result != null && result.Success)
|
|
|
|
|
? AuditStatus.Delivered
|
|
|
|
@@ -1409,13 +1407,13 @@ public class ScriptRuntimeContext
|
|
|
|
|
// (e.g. a shared script running inline).
|
|
|
|
|
actor: _sourceScript,
|
|
|
|
|
target: $"{systemName}.{methodName}",
|
|
|
|
|
// Audit Log #23: a sync one-shot call has no operation
|
|
|
|
|
// A sync one-shot call has no operation
|
|
|
|
|
// lifecycle, so CorrelationId is null. ExecutionId carries the
|
|
|
|
|
// per-execution id so all the sync ApiCall/DbWrite rows from
|
|
|
|
|
// one script run can be correlated together.
|
|
|
|
|
correlationId: null,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's
|
|
|
|
|
// (ParentExecutionId): the spawning execution's
|
|
|
|
|
// id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -1461,10 +1459,10 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WP-13: Helper for Database.Connection/CachedWrite syntax.
|
|
|
|
|
/// Helper for Database.Connection/CachedWrite syntax.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Audit Log #23 (M3 Bundle E — Task E6): <see cref="CachedWrite"/> mirrors
|
|
|
|
|
/// <see cref="CachedWrite"/> mirrors
|
|
|
|
|
/// <see cref="ExternalSystemHelper.CachedCall"/> — mints a
|
|
|
|
|
/// <see cref="TrackedOperationId"/>, emits the lifecycle's first
|
|
|
|
|
/// CachedSubmit packet (Channel <c>DbOutbound</c>), hands off to the S&F
|
|
|
|
@@ -1479,10 +1477,9 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly Guid _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// this run was inbound-API-routed; <c>null</c> for non-routed runs.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> ready for the Task 5
|
|
|
|
|
/// emitter — no audit row carries it yet.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> for audit-row stamping.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid? _parentExecutionId;
|
|
|
|
|
|
|
|
|
@@ -1491,7 +1488,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly ICachedCallTelemetryForwarder? _cachedForwarder;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M4 Bundle A): best-effort emitter for synchronous
|
|
|
|
|
/// Best-effort emitter for synchronous
|
|
|
|
|
/// <c>Database.Connection</c>-routed Execute / ExecuteScalar /
|
|
|
|
|
/// ExecuteReader calls. When wired, <see cref="Connection"/> returns
|
|
|
|
|
/// an <see cref="AuditingDbConnection"/> that intercepts each command
|
|
|
|
@@ -1503,7 +1500,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly IAuditWriter? _auditWriter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SourceNode-stamping (Task 14): the local cluster node name on
|
|
|
|
|
/// SourceNode-stamping: the local cluster node name on
|
|
|
|
|
/// which this script is executing (<c>node-a</c>/<c>node-b</c>).
|
|
|
|
|
/// Stamped onto <c>SiteCallOperational.SourceNode</c> at the
|
|
|
|
|
/// <c>Database.CachedWrite</c> CachedSubmit telemetry construction
|
|
|
|
@@ -1567,7 +1564,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
|
|
|
|
|
var inner = await _gateway.GetConnectionAsync(name, cancellationToken);
|
|
|
|
|
|
|
|
|
|
// Audit Log #23 (M4 Bundle A): wrap in an auditing decorator so
|
|
|
|
|
// Wrap in an auditing decorator so
|
|
|
|
|
// every script-initiated Execute* / ExecuteReader on the returned
|
|
|
|
|
// connection emits one DbOutbound/DbWrite audit row. The wrapper
|
|
|
|
|
// delegates all other ADO.NET behaviour to the inner connection
|
|
|
|
@@ -1588,7 +1585,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
sourceScript: _sourceScript,
|
|
|
|
|
logger: _logger,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's
|
|
|
|
|
// (ParentExecutionId): the spawning execution's
|
|
|
|
|
// id, threaded alongside _executionId. Null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId);
|
|
|
|
|
}
|
|
|
|
@@ -1623,7 +1620,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
name, trackedId, target, occurredAtUtc, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// M2.3 (#7): the gateway now attempts the write immediately and
|
|
|
|
|
// The gateway now attempts the write immediately and
|
|
|
|
|
// classifies the outcome (mirroring ExternalSystem.CachedCall). The
|
|
|
|
|
// result is retained because the immediate paths (WasBuffered=false —
|
|
|
|
|
// immediate success OR a synchronous permanent failure) bypass the
|
|
|
|
@@ -1638,12 +1635,12 @@ public class ScriptRuntimeContext
|
|
|
|
|
{
|
|
|
|
|
result = await _gateway.CachedWriteAsync(
|
|
|
|
|
name, sql, parameters, _instanceName, cancellationToken, trackedId,
|
|
|
|
|
// Audit Log #23 (ExecutionId Task 4): thread the script
|
|
|
|
|
// (ExecutionId): thread the script
|
|
|
|
|
// execution's ExecutionId + SourceScript so a buffered
|
|
|
|
|
// cached write's retry-loop audit rows carry them.
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
sourceScript: _sourceScript,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId Task 6): thread the
|
|
|
|
|
// (ParentExecutionId): thread the
|
|
|
|
|
// spawning inbound-API request's ExecutionId so a buffered
|
|
|
|
|
// cached write's retry-loop audit rows carry it too. Null
|
|
|
|
|
// for a non-routed run.
|
|
|
|
@@ -1658,7 +1655,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// M2.3 (#7): immediate-completion lifecycle — emit the missing
|
|
|
|
|
// Immediate-completion lifecycle — emit the missing
|
|
|
|
|
// Attempted + CachedResolve rows when the underlying write resolved
|
|
|
|
|
// without engaging the store-and-forward retry loop (immediate
|
|
|
|
|
// success or a synchronous permanent failure).
|
|
|
|
@@ -1673,7 +1670,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// M2.3 (#7): best-effort emission of the immediate-completion lifecycle
|
|
|
|
|
/// Best-effort emission of the immediate-completion lifecycle
|
|
|
|
|
/// for a <c>Database.CachedWrite</c> that resolved without the S&F
|
|
|
|
|
/// retry loop — emits an <c>Attempted</c> row then a terminal
|
|
|
|
|
/// <c>CachedResolve</c> row (<c>Delivered</c> on success, <c>Failed</c> on
|
|
|
|
@@ -1826,7 +1823,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// (TrackedOperationId); ExecutionId = per-execution id.
|
|
|
|
|
correlationId: trackedId.Value,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning
|
|
|
|
|
// (ParentExecutionId): the spawning
|
|
|
|
|
// execution's id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -1837,7 +1834,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
Channel: "DbOutbound",
|
|
|
|
|
Target: target,
|
|
|
|
|
SourceSite: _siteId,
|
|
|
|
|
// SourceNode-stamping (Task 14): the local node name
|
|
|
|
|
// SourceNode-stamping: the local node name
|
|
|
|
|
// (node-a/node-b) — threaded through INodeIdentityProvider
|
|
|
|
|
// at the ScriptExecutionActor; null when no provider was
|
|
|
|
|
// wired so central persists SiteCalls.SourceNode as NULL.
|
|
|
|
@@ -1892,31 +1889,30 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23: the per-execution id for this script run, stamped
|
|
|
|
|
/// The per-execution id for this script run, stamped
|
|
|
|
|
/// into <c>AuditEvent.ExecutionId</c> on the <c>NotifySend</c> row.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// this run was inbound-API-routed; <c>null</c> for non-routed runs.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> ready for the Task 5
|
|
|
|
|
/// emitter — no audit row carries it yet.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> for audit-row stamping.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid? _parentExecutionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M4 Bundle C): best-effort emitter for the
|
|
|
|
|
/// Best-effort emitter for the
|
|
|
|
|
/// <c>Notification</c>/<c>NotifySend</c> row produced when the script
|
|
|
|
|
/// calls <c>Notify.To(list).Send(...)</c>. Optional — when null the
|
|
|
|
|
/// <see cref="NotifyTarget"/> degrades to a no-op audit path so tests
|
|
|
|
|
/// / minimal hosts that don't wire AddAuditLog still work (mirrors the
|
|
|
|
|
/// M2 Bundle F <c>IExternalSystemClient</c> wrapper).
|
|
|
|
|
/// <c>IExternalSystemClient</c> wrapper).
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly IAuditWriter? _auditWriter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SourceNode-stamping (Task 13): the cluster node name on which this
|
|
|
|
|
/// SourceNode-stamping: the cluster node name on which this
|
|
|
|
|
/// script is executing — <c>node-a</c>/<c>node-b</c>. Stamped onto
|
|
|
|
|
/// <c>NotificationSubmit.SourceNode</c> by <see cref="NotifyTarget.Send"/>
|
|
|
|
|
/// so central can persist it on the <c>Notifications</c> row.
|
|
|
|
@@ -1975,16 +1971,16 @@ public class ScriptRuntimeContext
|
|
|
|
|
{
|
|
|
|
|
return new NotifyTarget(
|
|
|
|
|
listName, _storeAndForward, _siteId, _instanceName, _sourceScript, _logger,
|
|
|
|
|
// Audit Log #23: the per-execution id stamped into the
|
|
|
|
|
// The per-execution id stamped into the
|
|
|
|
|
// NotifySend row's ExecutionId column.
|
|
|
|
|
_executionId,
|
|
|
|
|
// Audit Log #23 (M4 Bundle C): forward the writer so Send()
|
|
|
|
|
// Forward the writer so Send()
|
|
|
|
|
// can emit one NotifySend(Submitted) row per accepted submission.
|
|
|
|
|
_auditWriter,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning execution's
|
|
|
|
|
// (ParentExecutionId): the spawning execution's
|
|
|
|
|
// id, threaded alongside _executionId. Null for non-routed runs.
|
|
|
|
|
_parentExecutionId,
|
|
|
|
|
// SourceNode-stamping (Task 13): the local node name, stamped
|
|
|
|
|
// SourceNode-stamping: the local node name, stamped
|
|
|
|
|
// onto NotificationSubmit.SourceNode in Send().
|
|
|
|
|
_sourceNode);
|
|
|
|
|
}
|
|
|
|
@@ -2064,21 +2060,20 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23: the per-execution id for this script run, stamped
|
|
|
|
|
/// The per-execution id for this script run, stamped
|
|
|
|
|
/// into <c>AuditEvent.ExecutionId</c> on the <c>NotifySend</c> row.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid _executionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// (ParentExecutionId): the spawning execution's id when
|
|
|
|
|
/// this run was inbound-API-routed; <c>null</c> for non-routed runs.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> ready for the Task 5
|
|
|
|
|
/// emitter — no audit row carries it yet.
|
|
|
|
|
/// Threaded alongside <see cref="_executionId"/> for audit-row stamping.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly Guid? _parentExecutionId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M4 Bundle C): best-effort emitter for the
|
|
|
|
|
/// Best-effort emitter for the
|
|
|
|
|
/// <c>Notification</c>/<c>NotifySend</c> row written immediately after
|
|
|
|
|
/// the underlying S&F enqueue accepts the submission. Optional —
|
|
|
|
|
/// when null no audit row is emitted (no-op path).
|
|
|
|
@@ -2086,7 +2081,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
private readonly IAuditWriter? _auditWriter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SourceNode-stamping (Task 13): the cluster node name on which this
|
|
|
|
|
/// SourceNode-stamping: the cluster node name on which this
|
|
|
|
|
/// script is executing (<c>node-a</c>/<c>node-b</c>). Stamped onto the
|
|
|
|
|
/// <c>NotificationSubmit.SourceNode</c> field in <see cref="Send"/> so
|
|
|
|
|
/// the central <c>NotificationOutboxActor</c> can persist it on the
|
|
|
|
@@ -2175,18 +2170,18 @@ public class ScriptRuntimeContext
|
|
|
|
|
// central audit trail. Null when no single script owns the context.
|
|
|
|
|
SourceScript: _sourceScript,
|
|
|
|
|
SiteEnqueuedAt: DateTimeOffset.UtcNow,
|
|
|
|
|
// OriginExecutionId (Audit Log #23): the SAME per-execution id stamped
|
|
|
|
|
// OriginExecutionId: the SAME per-execution id stamped
|
|
|
|
|
// onto this run's NotifySend audit row. It rides inside the serialized
|
|
|
|
|
// payload through the S&F buffer to central, where the dispatcher echoes
|
|
|
|
|
// it onto the NotifyDeliver rows so all rows for one run share an id.
|
|
|
|
|
OriginExecutionId: _executionId,
|
|
|
|
|
// OriginParentExecutionId (Audit Log #23): the SAME parent-execution id
|
|
|
|
|
// OriginParentExecutionId: the SAME parent-execution id
|
|
|
|
|
// stamped onto this run's NotifySend audit row — the spawning run's id
|
|
|
|
|
// for an inbound-API-routed execution, null otherwise. It rides through
|
|
|
|
|
// the S&F buffer to central, where the dispatcher echoes it onto the
|
|
|
|
|
// NotifyDeliver rows so the central rows carry the routed run's parent id.
|
|
|
|
|
OriginParentExecutionId: _parentExecutionId,
|
|
|
|
|
// SourceNode-stamping (Task 13): the cluster node name on which this
|
|
|
|
|
// SourceNode-stamping: the cluster node name on which this
|
|
|
|
|
// notification was emitted (node-a/node-b). Stamped from the local
|
|
|
|
|
// INodeIdentityProvider via ScriptExecutionActor. Rides inside the
|
|
|
|
|
// serialized payload through the S&F buffer to central, where
|
|
|
|
@@ -2210,7 +2205,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
"Notify enqueued notification {NotificationId} to list '{List}' for central delivery",
|
|
|
|
|
notificationId, _listName);
|
|
|
|
|
|
|
|
|
|
// Audit Log #23 (M4 Bundle C): emit one Notification/NotifySend
|
|
|
|
|
// Emit one Notification/NotifySend
|
|
|
|
|
// (Submitted) row per accepted submission. The emission is wired
|
|
|
|
|
// AFTER the EnqueueAsync returns so we only audit submissions the
|
|
|
|
|
// S&F engine accepted — a failed enqueue throws, never produces an
|
|
|
|
@@ -2248,7 +2243,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// mints the id via Guid.NewGuid().ToString("N") so the parse
|
|
|
|
|
// is expected to succeed; on the off-chance the format
|
|
|
|
|
// changes / a caller injects an unparseable value, leave it
|
|
|
|
|
// null per Bundle B's pattern rather than fail the emission.
|
|
|
|
|
// null rather than fail the emission.
|
|
|
|
|
Guid? correlationId = Guid.TryParse(notificationId, out var parsed) ? parsed : (Guid?)null;
|
|
|
|
|
|
|
|
|
|
// Capture the request summary — {"subject": "...", "body": "..."}.
|
|
|
|
@@ -2274,7 +2269,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
// lifecycle id; ExecutionId carries the per-execution id.
|
|
|
|
|
correlationId: correlationId,
|
|
|
|
|
executionId: _executionId,
|
|
|
|
|
// Audit Log #23 (ParentExecutionId): the spawning
|
|
|
|
|
// (ParentExecutionId): the spawning
|
|
|
|
|
// execution's id; null for non-routed runs.
|
|
|
|
|
parentExecutionId: _parentExecutionId,
|
|
|
|
|
sourceSiteId: string.IsNullOrEmpty(_siteId) ? null : _siteId,
|
|
|
|
@@ -2338,7 +2333,7 @@ public class ScriptRuntimeContext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Audit Log #23 (M3): script-side accessor for cached-operation tracking.
|
|
|
|
|
/// Script-side accessor for cached-operation tracking.
|
|
|
|
|
/// <c>Tracking.Status(trackedOperationId)</c> reads the site-local SQLite
|
|
|
|
|
/// row directly via <see cref="IOperationTrackingStore.GetStatusAsync"/> —
|
|
|
|
|
/// the site is the single source of truth for cached-call status, so no
|
|
|
|
|