Skip to content
bitzorcas
中EN

Guide

History, Archive, and Analytics

Activity trail, instance and business timelines, archive transaction, report calculation, query-port fallback, precision limits, and operations evidence.

Last updated

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.

Runtime operation

instance Version

task/execution mutation

ActivityRecord

Instance timeline

Business timeline

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.

Current archive 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.

IWorkflowQueryStore

instances

completed tasks

reject records

trend / instance stats

duration / approver / bottleneck

reject hotspots

Without QueryStore, instance statistics return zero values and many reports return empty lists.

Empty or zero resultRequired interpretation
Query adapter registered, bounded query returned no rowsvalid no-data result
Query adapter absent or failed to composecapability 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

Task duration report
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

Terminal window
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'

Next: migration and import

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%