The Web application now includes a full-page workflow designer, so authors do not have to maintain the entire JSON document by hand. The canvas remains an editor: the server is authoritative for node capabilities, business metadata, draft revisions, validation, and publication. A browser crash-recovery snapshot has no publication authority and cannot replace the database draft.
1. Designer data flow
The designer consumes @bitz/platform-sdk. Node types, field widgets, enum values, catalog data sources, and expression contexts come from backend contracts. Frontend models project those contracts into editable controls.
2. Current endpoints
| Method and route | Purpose | Permission |
|---|---|---|
GET /api/workflow/definitions/designer-schema | JSON Schema, seven node types, and field metadata | definition read |
GET /api/workflow/metadata/bindable-entities | Bindable business entities | workflow.metadata.read or implication |
GET /api/workflow/metadata/entities/{entityName}/flow-fields | FlowScope field catalog | workflow.metadata.read or implication |
GET/PUT/DELETE /api/workflow/definitions/{key}/draft | Read, save, or discard drafts | read / update / delete |
POST /api/workflow/definitions/{key}/draft/validate | Validate an exact saved revision | verify |
POST /api/workflow/definitions/{key}/draft/simulate | Simulate an exact revision with variables | use |
POST /api/workflow/definitions/{key}/draft/publish | Validate, create an immutable version, and update deployment | publish |
GET /api/workflow/definitions/{key}/versions | Version-dock list | read |
GET /api/workflow/definitions/versions/{definitionId} | Read an immutable version | read plus authoring scope |
workflow.definition.manage implies create, update, delete, publish, verify, and use, but deliberately does not imply read. workflow.definition.create implies update so a create-only role can save a revision-zero draft. A complete author role should still receive read plus the intended write capabilities explicitly.
3. Server-driven schema
WorkflowDesignerSchema returns the authoritative JSON Schema, registered node types, participant resolvers, service-task handlers, expression built-ins, timer actions, and listener types. A configuration field may declare:
- ValueType, Required, Description, Section, and Order;
- a Widget such as
participantBuilder,multiInstanceBuilder,rollbackBuilder,formFieldBuilder,dataScopeBuilder,timerBuilder, orlistenerBuilder; - EnumValues, nested Properties, and array ItemSchema;
- DataSource and ExpressionContext.
The service-task handler catalog may currently be empty. Show that state and retain controlled input rather than inventing implementations. Older clients may ignore new metadata, but a client-side node allowlist is never a publication security boundary.
4. AOT business metadata
Bindable entities do not come from runtime reflection. WorkflowBusinessMetadataProjector reads compile-time EntityMetaModel entries emitted for [BitzWorkflow(processKey)] and returns EntityName, ProcessKey, DisplayName, and Category, ordered by type short name.
The field catalog exposes only columns with a FlowScope display name. It may also include Group, IsReadOnly, FieldDataType, DataSourceKey, IsRequired, and MaxLength. Missing metadata, an unknown entity, or an empty catalog produces an empty list rather than a 500.
// ① ProcessKey becomes the stable definition key suggested by the designer.[BitzWorkflow("matter-intake", DisplayName = "Matter intake approval", Category = "Litigation")]public sealed class MatterIntake : TenantAggregateRoot<string>{ // ② Only fields explicitly marked for FlowScope enter the form-field catalog. [BitzFlowScope("Disputed amount", Group = "Case facts", IsReadOnly = true)] public decimal DisputeAmount { get; private set; }}Check the current BitzWorkflowAttribute and BitzFlowScopeAttribute definitions for exact parameter names. Rebuild and regenerate contracts after metadata changes. Runtime reflection does not hot-discover them.
5. businessBinding DSL
A definition root can contain:
{ "key": "matter-intake-approval", "businessBinding": { "entityName": "MatterIntake" }, "nodes": [ { "id": "partner", "type": "userTask", "name": "Lead partner approval", "formFields": [ { "fieldName": "DisputeAmount", "visibility": "readOnly" }, { "fieldName": "ApprovalComment", "visibility": "editable" } ] } ], "edges": []}EntityName is the compile-time catalog’s type short name. When the designer changes a binding, it preserves unknown extension properties under businessBinding; clearing EntityName removes the whole binding object.
6. Actual formFields boundary
formFields is a per-UserTask field-policy matrix. The designer combines existing DSL entries with the FlowScope catalog. It currently supports only editable, readOnly, and hidden; requiredness comes from the FlowScope metadata IsRequired value and is not part of the formFields DSL.
When metadata is available, the Validator checks EntityName and fieldName, but unknown items are warnings and do not block publication. This supports old definitions and version-skewed deployments; it does not create a universal runtime form engine. The owning business UI must consume and enforce the policy, and an unknown field must fail safely without bypassing owner field authorization.
7. Drafts and optimistic concurrency
A save contains Key, Name, JsonDefinition, optional BaseDefinitionId, ExpectedRevision, and DesignerStateJson. Creation uses revision 0. Every update submits the most recently returned Revision. The server checks the key, name, JSON root and embedded key, base-version visibility, and designer-state limits before saving.
If two tabs edit concurrently, the later save does not overwrite a newer revision; it receives a revision conflict. Prompt the author to reload or merge manually rather than forcing a local snapshot over server state.
8. Browser crash recovery
The frontend stores a dirty draft and canvas state as a local recovery snapshot. It exists only for a browser crash or accidental close:
- its storage key currently contains tenant and definition key, but no user identifier; two users sharing one browser profile in the same tenant can overwrite or see the same recovery snapshot;
- parse failure or snapshot-version mismatch discards it, and an older baseline is not offered after the server Revision advances;
- a recovered draft still saves against the server Revision;
- tokens, permission decisions, trusted TenantId, and publication state never enter the snapshot.
Shared workstations must therefore use separate operating-system accounts or browser profiles and clear recovery data at logout. The current implementation has neither time-based expiry nor user ID in the key; deployments that permit shared browser profiles should close both isolation gaps first.
9. Authoritative validation
ValidateWorkflowDefinitionDraft first reloads the authoritative draft by Key and ExpectedRevision and then calls the engine. Publication repeats the same validation. If any Error issue exists, it returns structured Validation with Published=false and neither creates a version nor changes a deployment.
Core checks cover node IDs, dangling edges, self-loops, start and end, ingress and egress, gateway conditions, participants, and reachability. Unknown business bindings and form fields are Warnings. Task switches, timer actions, and static expression types still need business-specific tests.
10. Simulation path
Simulation now returns PathNodeIds and VisitedEdgeRefs, which the frontend uses to highlight the canvas. It does not write the database, invoke listeners, or create tasks. It stops at a UserTask and has a maximum depth of 100.
Exclusive and inclusive preview select only the first matching edge, and parallel preview displays only the first path. A green highlight proves this input’s preview, not parallel joins, human task concurrency, or persisted execution.
11. Version dock and tenant scope
A Host author uses the PLATFORM scope; a tenant author uses the effective TenantId. The version list checks the authoring scope first. A tenant with no local version falls back to platform defaults, while Host never falls back to an arbitrary tenant version.
A DefinitionId lookup applies visibility again: Host sees only PLATFORM; a tenant sees its own and PLATFORM baseline versions; a foreign version becomes NotFound to prevent ID probing. Forking from a version applies the same base-version ownership check.
12. Publication semantics
The designer uses /draft/publish. It verifies the revision, runs authoritative validation, creates or reuses an immutable version, validates DefinitionId, Version, and Checksum, and updates the tenant/office deployment only when the binding changed.
The repository still has the direct management endpoint POST /api/workflow/definitions/. That older path requires compilable JSON but does not run the designer’s complete validation step. Automation that calls it must invoke /validate first; new UI should prefer the draft-publication flow.
13. Frontend permissions and errors
Gate buttons by the same actions as the server: save with update/create, validate with verify, simulate with use, publish with publish, and delete with delete. The ability to edit does not imply permission to validate or simulate.
Use NodeId and EdgeRef to locate validation issues on the canvas and distinguish Warning from Error. Network failure, a 409 revision conflict, and a 403 authorization denial must not collapse into one generic “save failed” message because their safe recovery actions differ.
14. Verification commands
# ① Backend: schema, AOT metadata, draft actions, authoring scope, and paths.dotnet test tests/BitzOrcas.Unit.Tests \ --filter "FullyQualifiedName~WorkflowBusinessMetadata|FullyQualifiedName~DefinitionSimulator"dotnet test tests/BitzOrcas.Application.Tests \ --filter FullyQualifiedName~Workflow
# ② Frontend: recovery, builders, path highlighting, and full-page routing.cd frontendyarn workspace @bitz/app test workflow-designer-recovery workflow-builder-models workflow-definition-runtime navigation-contract15. Release checklist
- Unknown schema and widget values degrade safely.
- AOT metadata exposes only owner-declared entities and fields.
- Positive and negative tests cover create/update/read/verify/use/publish/delete combinations.
- Cross-tenant DefinitionId and Host PLATFORM version-list regressions are locked down.
- Crash snapshots contain no credentials; shared-browser deployments separately verify user isolation and expiry cleanup.
- Errors block publication; every Warning has an explicit accept-or-fix record.
- Simulation remains preview evidence; real execution covers both ORMs, concurrency, and recovery.