Skip to content
bitzorcas
中EN

Guide

Workflow Migrator

Convert legacy workflow definitions from bitzorcas or saury into the new JSON DSL, write sidecar reports, and Deploy plus Publish on an isolated target.

Last updated

BitzOrcas.Workflow.Migrator is platform Tooling that migrates workflow definitions only. It reads either the BitzOrcas five-table set or the Saury T_WorkflowDefinition* tables, selected by source, and converts them to the new engine JSON DSL. Outside dry run it calls the real Workflow IRepositoryService for DeployAsync and PublishDeploymentAsync. Saury table knowledge stays in Tooling and must not enter Framework or Platform runtime projects.

Migration stages

yesnobitzorcassaurysetemptyyesno

Approved configuration

source set?

Use explicit bitzorcas or saury

Detect FlowDefine or T_WorkflowDefinition

Source

Read five Flow* tables

Read Saury definition tables

FlowDefinitionConverter

SauryFlowConverter

Console warnings and JSON DSL

sidecar migration report

output-dir?

Write json and markdown

Console only

DryRun?

Manual semantic review

Deploy new version

Publish tenant or office bindings

Target-tenant smoke and regression

How to read the chart: after configuration is approved, resolve the source, then split by table family. BitzOrcas keeps the existing five-table converter unchanged. Saury uses the step-tree converter and also writes a sidecar report. output-dir only controls disk output. DryRun decides whether to deploy. Warnings do not increment the failure count, and a successful conversion is not semantic equivalence.

Prepare

  1. The source database is a read-only snapshot with a recorded time and schema version.
  2. The target is an isolated migration database, not a production tenant database.
  3. Choose source. When omitted, detection uses FlowDefine for bitzorcas and T_WorkflowDefinition for saury. Both tables fall back to bitzorcas. Neither table throws and exits with 99.
  4. Set flow-names explicitly. BitzOrcas filters FlowDefine.Name; Saury filters T_WorkflowDefinition.Name. Empty migrates every active definition.
  5. For Saury office-scoped permissions and tenant bindings, prepare an integer office-id.
  6. Freeze legacy definition edits; the tool does not lock source tables.
  7. Define instance cutover; this tool does not move T_Workflow or BitzOrcas in-flight instances.
Terminal window
# Build the tool and Workflow dependencies to expose metadata or adapter drift.
dotnet build src/Tooling/BitzOrcas.Workflow.Migrator \
--configuration Release
# Converter and DSL compile tests.
dotnet test tests/BitzOrcas.Workflow.Migrator.Tests \
--configuration Release

Missing either connection string prints usage and returns 1. There is no dedicated help switch.

Configuration wiring

The program loads appsettings.json, appsettings.local.json, MIGRATOR_ environment variables, and the command line, binds the Migrator section, then applies kebab-case switches through MigratorCli.ApplyOverrides. Command-line switches win over the already bound JSON and environment values.

{
"Migrator": {
"SourceConnectionString": "Server=legacy-staging;Database=WorkflowSnapshot;User Id=wf_migrator;Password=Secr3t!P@ss;TrustServerCertificate=True",
"TargetConnectionString": "Server=target-isolated;Database=WorkflowMigration;User Id=wf_migrator;Password=Secr3t!P@ss;TrustServerCertificate=True",
"TenantId": "PLATFORM",
"DryRun": true,
"FlowNames": "MatterApproval,SealApply",
"Source": "saury",
"OfficeId": "1",
"OutputDir": "/tmp/wf-migrator-out"
}
}

Save secrets as appsettings.local.json in the tool directory and confirm Git ignores it. Password/Pwd is masked in the summary log, but exception, shell, and driver logs remain sensitive artifacts.

SwitchOptions keyMeaning
source-connection / target-connectionSourceConnectionString / TargetConnectionStringSource and target databases
sourceSourcebitzorcas or saury; empty triggers detection
office-idOfficeIdSaury only: filter permissions and T_TenantWorkflowDefinition
output-dirOutputDirWrite {key}.v{version}.json and the sidecar report
flow-namesFlowNamesComma-separated flow names
tenant-idTenantIdTarget tenant, default PLATFORM
dry-runDryRunConvert without deploy

Implement

Run from the tool project directory so local configuration is loaded.

Terminal window
cd src/Tooling/BitzOrcas.Workflow.Migrator
# Existing BitzOrcas invocation: omit source and stay on the five-table path.
dotnet run --configuration Release -- \
--dry-run \
--output-dir /tmp/wf-bitzorcas
# Saury product database: explicit source, optional name and office filters.
dotnet run --configuration Release -- \
--source saury \
--dry-run \
--office-id 1 \
--flow-names MatterApproval \
--output-dir /tmp/wf-saury

For both sources, output-dir writes {key}.v{version}.json, {key}.v{version}.migration-report.md, and {key}.migration-report.md. Omit it to keep console-only output.

BitzOrcas five-table path

FlowDefine selects IsDeleted = 0 OR IsDeleted IS NULL. Per definition the tool reads nodes ordered by NodeStep, edges with LaneType = 0, conditions joined through FlowControl, and all approvers. FlowDefinitionConverter node, countersign, rollback, and condition mapping is unchanged by the Saury path.

Legacy NodeTypeJSON DSL typeNotes
StartstartEventEntry
ApproveruserTaskApproval task
FinishendEventExit
other valuesuserTaskWarning only, then degrade

Conditions map GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, Equal, and NotEqual. Contains returns empty, so the edge has no condition.

Saury definition path

SauryFlowReader reads T_WorkflowDefinition, T_WorkflowDefinitionStep, T_WorkflowDefinitionState, T_WorkflowPermission, T_WorkflowOrganizationUnits, and T_TenantWorkflowDefinition. It does not read T_Workflow instances. Only IsActive = 1 definitions are loaded, ordered by Name then Version.

Version resolution matches DefinitionManager.GetDefinitionState: prefer the DefinitionId bound in T_TenantWorkflowDefinition, otherwise fall back to Version = 1, then order by Version descending. Publish uses the highest Version with a null office when there is no binding; bound offices publish the matching version.

SauryNew DSLRule
Name / Version / display namekey / version / namekey = SanitizeKey(Name), same cleanup as BitzOrcas
Top-level step chainnodes + edgesPrepend start, append end
WaitForuserTaskMap permissions by WaitForId; office-id filters OfficeId; missing permission omits participants and warns
T_WorkflowOrganizationUnitsdataScopeOfficeLevel 1/2/3 maps to Tenant/Office/Department; non-empty org lists map to Custom
IfexclusiveGatewayConsecutive If steps become one gateway; Condition is translated mechanically
Action type name Return / RedorollbackReturn prefers mode: start; Redo uses prev
Action type name Approve / Confirm / EndFolded into an edgeSide effects stay out of the DSL; StateId goes to the report
Leading ApplyFoldedApplicant submit is starter logic
While / ForEach / Schedule / RecurNot migratedWarning only
T_WorkflowDefinitionStateSidecar state mapNot written into the DSL

Control primitives are detected by the WorkflowCore.Primitives. prefix, with or without an assembly suffix. Condition translation keeps Data.X / data.X, comparison and logical operators, numbers, quoted strings, and true / false / null. Method calls such as .Contains(value) are not translated: the edge has no condition and the original text goes to warnings plus the sidecar.

Sidecar migration report

Each Saury definition writes one Markdown file for business modules implementing IBusinessIntegrationCallback.OnStatusChangedAsync against BusinessStatusChange.ToFlowState and ToNodePrefix:

  1. State map: legacy StateId / Name / DisplayName / IsAudit / Category versus new NodePrefix and suggested FlowState.
  2. Hook list: each business step Name, DisplayName, full StepType, IsAudit, owning WaitFor, and suggested write-back state. StepType is read from the database; Saury source is not parsed.
  3. Translation failures: untranslated Condition text, skipped control primitives, userTasks without participants, and degraded dataScope.

The BitzOrcas path also writes a report when output-dir is set, but the state map and hook list stay empty and the failure list only collects converter warnings.

Deploy and publish

Outside dry run the tool builds the engine on the SqlSugar Workflow store. DeployAsync(key, name, jsonDsl, "migrator", tenantId) creates an immutable version; the returned DefinitionId is passed to PublishDeploymentAsync. BitzOrcas still publishes with a null office. Saury passes each bound office as a string.

These two calls are not one atomic transaction owned by the migrator. A successful Deploy plus a failed Publish can leave an unpublished version. Query target versions and bindings before rerunning.

Terminal window
# After the isolated target is confirmed, turn DryRun off.
dotnet run --configuration Release -- \
--source saury \
--output-dir /tmp/wf-saury \
> "$MIGRATION_EVIDENCE/workflow-deploy.log" 2>&1
rg -n 'Deploy|Publish|failed' \
"$MIGRATION_EVIDENCE/workflow-deploy.log"

The tool prints Chinese status lines. Match those exact phrases from the zh page when grepping a real run.

Verify

  1. Every dry-run JSON deserializes and compiles through DefinitionCompiler; see tests/BitzOrcas.Workflow.Migrator.Tests.
  2. The sidecar has all three sections, and Hook StepType matches the database type name.
  3. The same input produces byte-identical JSON and reports on two runs.
  4. Invocations without source still use the BitzOrcas five-table path.
  5. A WaitFor without permissions only warns; it does not invent assignees.
  6. Method-call conditions are not written onto edges.
  7. The target tenant can start a new instance from the published binding.
Terminal window
# Converter unit tests and Workflow-related architecture contracts.
dotnet test tests/BitzOrcas.Workflow.Migrator.Tests \
--configuration Release
dotnet test tests/BitzOrcas.Architecture.Tests \
--configuration Release \
--filter 'FullyQualifiedName~Workflow'

Roll back

  1. Keep dry-run logs, JSON, and sidecar files as approval artifacts.
  2. Unpublished definitions on the isolated target can be removed or disabled; do not blindly rerun against production.
  3. Published bindings roll back with RollbackDeploymentAsync to PreviousDefinitionId. Already started instances stay locked to the old snapshot.
  4. The source remains a read-only snapshot; this tool never writes Saury or BitzOrcas legacy tables.
  5. Business callback code belongs to the business module and is not rolled back with the tool.

Exit codes

CodeCurrent meaningAutomation
0No flow threw; warnings may still existContinue semantic review; do not auto-promote
1Missing connection stringsBlock and fix configuration
2At least one convert or deploy failedBlock and reconcile partial success
3Ctrl+C / cancellationBlock and inspect partial deploy
99Unhandled exception or source detection failureBlock and keep masked logs

A single-flow failure does not stop the batch, so code 2 can mean partial success.

Delivery checklist

  • Snapshot source and isolated target identities are traceable.
  • The explicit or detected source is recorded.
  • Every dry-run warning has an accept, fix, or block decision.
  • Saury sidecar sections were handed to the business module for callback work.
  • Definition key, version, Deploy, and Publish bindings were checked on the target.
  • Instance migration is explicitly out of scope, with a cutover plan.
  • Logs, JSON, and reports are stored as sensitive artifacts.

See also

100%

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