Workflow history combines ActivityRecord, live instance views, and archived instances. It explains execution but is not currently a signed, immutable compliance ledger; notification, callback, timer attempt, and external delivery evidence are not one chain.
1. Read ports
IWorkflowHistoryQuery exposes instance trail, live/archive summary, instance timeline, business-key aggregation, business status history, and current summary. IWorkflowArchivePort moves terminal instances. IWorkflowAnalyticsPort calculates instance, duration, approver, bottleneck, rejection, trend, and deployment reports.
2. Activity records
Runtime operations append action, source/target node, executor, recipient, comment, state before/after, and timestamp. Most are inside the runtime transaction.
Notification results, callback responses, timer attempts, and cache invalidations lack a common durable correlation record.
3. Instance and timeline
GetInstance reads archive first, then live. A live terminal EndTime uses the latest activity timestamp and remains null when no record exists.
GetTimelineView first loads the live instance. Archive deletes that row, so an archived instance can have a history summary but an empty timeline. This requires implementation and integration-test correction.
Business timeline accepts businessKey without tenantId. Isolation depends on the active adapter context.
4. Archive
Archive rejects Running/Suspended, returns success if a history row already exists, then saves history and deletes the live instance in a transaction.
await using var transaction = await store.BeginTransactionAsync(cancellationToken);// Persist the minimal summary before removing the live instance.await store.SaveHistoryInstanceAsync(history, cancellationToken);await store.DeleteInstanceAsync(instanceId, cancellationToken);// This transaction does not explicitly delete every live child row.await transaction.CommitAsync(cancellationToken);It does not explicitly delete executions, tasks, candidates, or timers. Depending on schema cascades, this creates orphans or removes needed history. Test both real ORM schemas. DefinitionName is set to DefinitionKey and EndReason to status text.
5. Report source
IWorkflowQueryStore returns instances, completed tasks, rejection records, and deployment statistics. Engine code computes duration, approver, bottleneck, rejection, and trend views in memory.
Without QueryStore, instance statistics return zero values and many reports return empty lists.
| Empty or zero result | Required interpretation |
|---|---|
| Query adapter registered, bounded query returned no rows | valid no-data result |
| Query adapter absent or failed to compose | capability unavailable; do not render as business zero |
6. Duration accuracy
Task-duration reports can derive complete minus create time. Trend currently counts terminal instances created in the period and always sets AvgDuration to TimeSpan.Zero. StatisticsAggregationJob also writes zero average and maximum duration.
7. Timezone and aggregation
Trend queries no longer fill missing endpoints with DateTimeOffset.MinValue or the current clock. GetTrendReportQueryRule requires both From and To and rejects From later than To with Workflow.Report.PeriodInvalid; valid values are forwarded unchanged to IWorkflowAnalyticsPort. There is no maximum-span rule yet, so callers should still use a bounded window.
StatisticsAggregationJob constructs a UTC day from date.Date and groups by definition, tenant, and office. Tenant-local reporting needs explicit timezone conversion, DST behavior, and late-data repair.
Stable statistic IDs support overwrite-style rerun, but range execution has no checkpoint or lease.
8. Query example
var filter = new ReportFilter{ // Every report needs a trusted tenant and a bounded time window. TenantId = currentTenantId, DefinitionKey = "matter-intake-approval", From = fromUtc, To = toUtc, TopN = 20};
IReadOnlyList<TaskDurationReport> rows = await analytics.GetTaskDurationReportAsync( filter, cancellationToken);
// Empty can mean no data or no query adapter.9. Privacy and retention
Comments, variables, and task names can contain personal or sensitive business data. Workflow has no complete retention/erasure orchestration across live tables, archive, trail, reports, cache, and backups.
Define retention, legal hold, comment redaction, aggregate identifiability, and delete-after-restore procedures before commercial compliance claims.
10. Operations metrics
Track active/pending/overdue, task age, timer pending/fired/failure, archive backlog, QueryStore latency, rows scanned, cache hit, and stale intervals. Never use taskId, businessKey, comment, or userId as metric labels.
11. Tests
- live/archive read consistency and post-archive timeline;
- repeat archive and transaction interruption;
- provider cascade/orphan parity;
- explicit missing-QueryStore degradation;
- timezone, late completion, and large range;
- percentile edge cases;
- same businessKey across tenants;
- retention and legal hold.
12. Source checks
rg -n "ArchiveAsync|SaveHistoryInstance|DeleteInstanceAsync" src/Framework/BitzOrcas.Workflow -g '*.cs'
rg -n "TimeSpan.Zero|AvgDurationSeconds = 0|QueryStore" src/Framework/BitzOrcas.Workflow/BitzOrcas.Workflow.Engine -g '*.cs'