Workflow has two distinct migration paths. StartAtNode executes a node through the current engine. ImportInstance directly reconstructs an old instance, executions, tasks, and one import activity. The first fits process adoption; the second fits in-flight legacy migration.
1. Choose a path
| Need | Operation |
|---|---|
| Existing business record continues at a new-definition node | StartAtNode |
| Existing completed record gets a completed process | StartAtNode(autoComplete=true) |
| Preserve old assignee and task creation time | ImportInstance |
| Move current applicant after staff departure | TransferApplicant / TransferAllByUser |
| Repair a running instance to another node | ResetToNode with strict Manage permission |
2. StartAtNode
It resolves the active definition, validates target node, creates instance and start activity, and either executes the target node or marks the instance completed.
Result<IProcessInstance> migrated = await migration.StartAtNodeAsync( // The business key must reconcile one-to-one with the legacy case record. "matter-intake-approval", legacyMatter.MatterNo, "MatterIntake", legacyMatter.ApplicantId, currentTenantId, legacyMatter.OfficeId, "partner-review", autoComplete: false, // Validate required names and value types before this write path. mappedVariables, "Legacy migration batch 2026-08", cancellationToken);Although comments say preceding nodes are marked skipped/completed, current code writes one Start activity pointing to the target; it does not create a record for every skipped node. There is no source idempotency key.
3. Import request
The request carries source system and ID, definition key, tenant and office, business identity, starter, current node, FlowState, Status, variables, active tasks, and original start time.
{ "sourceSystem": "legacy-oa", "sourceInstanceId": "WF-2021-00981", "definitionKey": "matter-intake-approval", "tenantId": "tenant-a", "officeId": "shanghai", "businessKey": "(2026)HU-ZH Case No. 0981", "businessType": "MatterIntake", "starterId": "1001", "currentNodeId": "partner-review", "currentNodeName": "Lead partner review", "flowState": "BW", "status": "Running", "variables": { "disputeAmount": 1850000 }, "activeTasks": [ { "nodeId": "partner-review", "nodeName": "Lead partner review", "assigneeId": "2008", "taskStatus": "Pending", "createTime": "2026-06-20T02:00:00Z" } ], "originalStartTime": "2026-06-18T08:00:00Z"}The HTTP command is authorized, but TenantId is taken from the request object rather than CurrentUser. Cross-tenant import needs explicit platform authorization and audit. The import command also implements ILicensedMigrationRequest, so commercial in-flight migration passes through the Runtime License gate — an unlicensed deployment cannot reach this entry point.
4. Definition selection defect
ManagementService lists definitions and selects versions[0], without using deployment binding or explicit sorting. Import can pin the wrong version. Require DefinitionId/version and validate every target node. Candidate refresh has the same version-selection issue.
5. Non-transactional write
Import saves instance, then each execution and task, then the activity record without BeginTransaction.
Put one item in one transaction and add a unique tenant+sourceSystem+sourceInstanceId import record.
6. Missing preflight
Current import does not validate current/task nodes, assignee tenancy, consistent statuses, existing business process, variable policy, or duplicate source ID. Invalid status text silently falls back to Running/Pending.
A dry-run report should classify blocking errors and signed warnings before writes.
7. Batch import
ImportInstancesBatch runs sequentially, wraps item failures in a successful batch Result, and reports transient progress. It has no durable checkpoint, pause/resume, or failure export.
Persist a batch manifest containing source snapshot checksum, mapping version, operator/approver, and item source ID to status/new ID/error. Restart only unfinished items and replay success from idempotency records.
8. Applicant transfer
TransferApplicant permits the current applicant or Manage authorization and transactionally updates applicant plus trail. TransferAllByUser iterates active instances and returns per-item failures; it is not all-or-nothing.
9. Safe runbook
- Snapshot and freeze legacy workflow writes.
- Run a small dry-run and approve mappings.
- Record every source-to-new identity.
- Stop at the first unexpected partial state.
- Clean only records tagged to the audited batch.
- Reconcile business key, task count, assignee, state, and trail.
- Complete a sample task in the new engine.
- Rehearse rollback before production.
10. Acceptance
- source count equals success plus explicit failure;
- one source maps to one new instance;
- definition version is correct;
- every pending task is visible and operable by the intended user;
- no cross-tenant visibility;
- process continues after sample completion;
- timer, notification, and cache state is rebuilt;
- replay creates no duplicates;
- rollback is tested.
11. Source checks
rg -n "ImportInstanceAsync|SaveInstanceAsync|SaveExecutionAsync|SaveTaskAsync" src/Framework/BitzOrcas.Workflow/BitzOrcas.Workflow.Engine/Services/ManagementService.cs
rg -n "SourceInstanceId|BeginTransactionAsync|versions\[0\]" src/Framework/BitzOrcas.Workflow src/Platform/Workflow -g '*.cs'Definitions themselves come from the Workflow Migrator, which reads BitzOrcas or Saury legacy tables into JSON DSL. Import on this page is complementary: publish a definition first, then import in-flight instances.