feat(auditlog): site script-side emitters stamp ParentExecutionId

This commit is contained in:
Joseph Doherty
2026-05-21 17:45:55 -04:00
parent 6af2607a50
commit 150ba5e63f
9 changed files with 343 additions and 60 deletions

View File

@@ -60,7 +60,8 @@ public class ExternalSystemCallAuditEmissionTests
private static ScriptRuntimeContext.ExternalSystemHelper CreateHelper(
IExternalSystemClient client,
IAuditWriter? auditWriter,
Guid executionId)
Guid executionId,
Guid? parentExecutionId = null)
{
return new ScriptRuntimeContext.ExternalSystemHelper(
client,
@@ -69,7 +70,9 @@ public class ExternalSystemCallAuditEmissionTests
executionId,
auditWriter,
SiteId,
SourceScript);
SourceScript,
cachedForwarder: null,
parentExecutionId: parentExecutionId);
}
[Fact]
@@ -230,6 +233,48 @@ public class ExternalSystemCallAuditEmissionTests
// a sync one-shot call has no operation lifecycle.
Assert.Equal(TestExecutionId, evt.ExecutionId);
Assert.Null(evt.CorrelationId);
// Audit Log #23 (ParentExecutionId): null for a non-routed run — the
// default CreateHelper supplies no parentExecutionId.
Assert.Null(evt.ParentExecutionId);
}
[Fact]
public async Task Call_RoutedRun_StampsParentExecutionId_FromContext()
{
// Audit Log #23 (ParentExecutionId, Task 5): an inbound-API-routed run
// carries the spawning execution's id; the sync ApiCall row must stamp
// it in ParentExecutionId alongside its own fresh ExecutionId.
var client = new Mock<IExternalSystemClient>();
client
.Setup(c => c.CallAsync("ERP", "GetOrder", It.IsAny<IReadOnlyDictionary<string, object?>?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExternalCallResult(true, "{}", null));
var writer = new CapturingAuditWriter();
var parentExecutionId = Guid.NewGuid();
var helper = CreateHelper(client.Object, writer, TestExecutionId, parentExecutionId);
await helper.Call("ERP", "GetOrder");
var evt = Assert.Single(writer.Events);
Assert.Equal(parentExecutionId, evt.ParentExecutionId);
Assert.Equal(TestExecutionId, evt.ExecutionId);
}
[Fact]
public async Task Call_NonRoutedRun_ParentExecutionIdIsNull()
{
// A normal (tag/timer) run is not routed — no parent id supplied, so
// the emitted ApiCall row's ParentExecutionId stays null.
var client = new Mock<IExternalSystemClient>();
client
.Setup(c => c.CallAsync("ERP", "GetOrder", It.IsAny<IReadOnlyDictionary<string, object?>?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExternalCallResult(true, "{}", null));
var writer = new CapturingAuditWriter();
var helper = CreateHelper(client.Object, writer);
await helper.Call("ERP", "GetOrder");
var evt = Assert.Single(writer.Events);
Assert.Null(evt.ParentExecutionId);
}
[Fact]