Alterspective · Workflow Action

Copy Fields Between Work Items

Copy mapped work-item attributes, core fields, and aspect data between work items in one configurable workflow action.

Workflow Action T2 Deployed Configurable
Overview

Copy Fields Between Work Items is a configurable Visual Workflow action that moves selected data from one work item to another without requiring workflow authors to build a chain of custom HTTP calls.

It is designed for practical workflow automation scenarios where a new item is spawned, an existing item is synchronised, or staged forms on the same item need to share data. In one action, authors can copy:

  • FormBuilder-style work-item attributes
  • Core work-item fields such as title, description, reference, category, and priority
  • Aspect data paths using a safe read-merge-write approach
  • Participant assignments through compatible source and destination roles

Why it matters

Workflow authors often need to carry forward important context from an intake, enquiry, parent matter, or source record into a destination record. Historically, that could mean several separate nodes: one for attributes, another for title or reference, and another custom integration step for aspect data.

This action consolidates that work into one mapped step. Authors choose the source and destination work-item identifiers, define the fields to copy, and let the action route each mapping to the correct Sharedo public API.

Business benefits

  • Fewer workflow nodes — replace multiple update steps with one configurable copy action.
  • Clearer workflow design — mappings are visible in the action designer instead of hidden in custom script.
  • Safer data movement — strict and lenient modes let teams decide whether missing source data should stop the workflow or be skipped.
  • Verified outcomes — the action reads every written route back before it lets the workflow follow success or partial, with PII-safe source → destination status in the execution log.
  • Better authoring experience — one Mapping Studio lets authors map fields and participant roles with the same source-to-destination interaction.
  • Flexible transformation — simple transforms and Excel-style expressions support common formatting and composition needs.
  • Participant safety — allowed identity types and single/multiple role

rules are checked at design time and again at runtime.

  • Honest API disclosure — field writes/readback and participant writes use

public APIs; current participant-role validation and typed preflight/readback use disclosed internal Data Composer schema and graph queries that are revalidated on upgrades.

Typical use cases

Intake to instruction

Copy intake data from a source work item into a newly created destination work item. For example:

  • Caller name and contact details as attributes
  • Destination title from a mapped scope field
  • Priority or summary into aspect data

Parent to child work item

When a child item is created from a parent, copy selected values such as reference, category, priority, and structured case overview fields.

Same-work-item form prefill

Use the same work-item id as both source and destination to copy values between forms or stages on the same work item.

Computed references and titles

Use expressions to build values such as:

  • A destination reference based on the source reference
  • A title composed from multiple source fields
  • A cleaned or formatted description

Participant propagation

Copy selected participant roles when a workflow creates or synchronises a related work item. The action can copy all compatible identities into a multiple role, or select one deterministically for a single role. Existing destination participants are preserved and every attempted assignment is read back before the workflow continues. A retry also re-verifies assignments that were already present, and a single-valued role is treated as occupied by any existing assignment. The Studio presents exact, partial, and blocked mappings with visible identity-type, cardinality, top-one, and security-team consequences.

What can be copied

Mappings are routed by the destination key:

Destination formatWrites toExample
firstNameAttributefirstName
attr:firstNameAttributeattr:intakeSource
core:titleCore work-item fieldcore:title
core:referenceCore work-item fieldcore:reference
aspect:case-overview.summaryAspect data pathaspect:case-overview.summary

Bare destination keys are treated as attributes for backwards compatibility.

Transform and expression support

Single-source mappings can apply common transforms:

  • Trim whitespace
  • Uppercase
  • Lowercase
  • Date only
  • Null if empty

Expression mappings can combine values using a small Excel-style language. Examples include:

  • concat({firstName}, " ", {lastName})
  • uppercase({matterCode})
  • default({preferredName}, {firstName})
  • format-date({createdOn}, "YYYY-MM-DD")
  • concat({core:reference}, "-SOW")

Expressions can reference attributes, core fields, and aspect paths.

Operating modes

Strict mode

Strict mode is the default. If a required single-source mapping is missing from the source item, the action routes to failed and does not write partial data.

Use strict mode when copied data is mandatory for downstream workflow steps.

Lenient mode

Lenient mode skips missing single-source mappings, writes the remaining values, and routes to partial when anything was skipped.

Use lenient mode when source data is optional or tenant forms vary by scenario.

Outputs

The action provides three workflow outlets:

  • success — all requested mappings were copied and matched on destination read-back
  • partial — lenient mode skipped one or more missing source fields, and every remaining write matched on read-back
  • partial also covers configured participant skips, deterministic

multi-to-single truncation, or an already occupied destination single role

  • failed — required data was missing, an API call failed, or a persisted value did not match

Important limitations

  • Auto-map applies to attributes only; it does not automatically copy core fields or aspect data.
  • Single-source mappings can read attributes, core fields, aspect paths, and workflow variables by using their supported prefixes.
  • core:userTitle is readable as a source but is not offered as a destination because the public update API silently ignores it after creation.
  • Participant fields embedded inside FormBuilder attributes are still complex

values and are not coerced. Use the dedicated participant-role mapping section for actual work-item participants.

  • Participant copying is additive only. It does not remove or replace existing

destination participants, and security-team mappings can grant visibility.

  • Participant mappings require configured String result and error variables so

workflows can route and audit the verified outcome explicitly.

  • Core work-item writes are limited to the public API field whitelist supported by the platform.
  • Aspect writes use read-merge-write to preserve sibling fields, but tenant-specific aspect handlers should still be tested.
Technical

Technical notes

Component identity

  • System name: AltCopyFieldsBetweenWorkTypes
  • Designer widget: Alt.CopyFieldsBetweenWorkTypesDesigner
  • Kind: Visual Workflow action
  • Category: Alterspective
  • Icon: fa-copy
  • Source path: src/vw-actions/copy-fields-between-work-types
  • Foundry-backed: No
  • Runtime dependency style: Native TypeScript action template using Sharedo workflow runtime globals, public work-item writes/readback, and disclosed internal Data Composer schema/graph APIs

Runtime purpose

The action copies mapped values from a source work item to a destination work item. Version 1.6 expands the original attribute-copy behaviour into multi-category routing:

  • Attributes via /attributes
  • Core work-item fields via workItem body
  • Aspect data via aspectData body
  • Participant assignments via a typed role-mapping phase (v2.0)

Routing is determined per mapping from the destination key prefix.

APIs used

The template uses public Sharedo work-item endpoints:

  • GET /api/v1/public/workItem/{sourceId}/attributes
  • GET /api/v1/public/workItem/{sourceId} when expressions reference source core: or aspect: data
  • GET /api/v1/public/workItem/{destinationId} when aspect writes require read-merge-write
  • PUT /api/v1/public/workItem/{destinationId} for core fields and aspect data
  • POST /api/v1/public/workItem/{destinationId}/attributes for attribute writes
  • GET /api/v1/public/workItem/{destinationId} after writes to verify core/aspect persistence
  • GET /api/v1/public/workItem/{destinationId}/attributes after writes to verify attribute persistence
  • GET /api/admin/sharedoTypes/{type}/participantRoles and

GET /api/participantRoles/sharedoType/{type} in the designer to join allowed identity types with cardinality (internal)

  • GET /api/graph/sharedo/schema/{type} to revalidate current participant

role presence, allowed identity types, and cardinality before mutation (internal Data Composer schema)

  • POST /api/graph/workitem/query for typed source/destination participant

preflight and post-write read-back (internal Data Composer graph query)

  • POST /api/v1/public/workItem/{destinationId}/participants for additive

participant writes

Writes are sequential. Core/aspect PUT is performed before attribute POST. No success or partial outlet is fired until every requested write has been read back and deep-compared. A verification read failure or mismatch routes to failed.

If the core/aspect write succeeds but the later attribute write fails, the destination may be partially updated; the failure log explicitly notes this.

Configuration shape

The persisted step config is represented by StepConfig:

interface StepConfig {
  schemaVersion?: 2;
  sourceWorkItemIdVariable: string;
  destinationWorkItemIdVariable: string;
  sourceWorkType: string;
  destinationWorkType: string;
  fieldMap: FieldMapEntry[];
  mode: "strict" | "lenient";
  autoMap: boolean;
  ignorePrefixes: string[];
  participantMappings?: ParticipantRoleMapping[];
  participantResultVariableName?: string;
  errorVariableName?: string;
}

sourceWorkType and destinationWorkType drive design-time metadata and are also required by participant schema validation and graph reads. Field-only configurations continue to read and write by work-item id.

Participant mappings use preserve-and-fill; v2.0 never deletes destination participants. The runtime revalidates saved allowed-type/cardinality contracts against the current Data Composer schema, then reads ods.type.systemName from the typed graph, deduplicates by role/effective type/ODS id, and sorts top-one candidates by non-null RoleOrder then stable assignment id. Duplicate mappings to one single-valued destination role fail before mutation. Any existing assignment occupies a single-valued role even if its identity type cannot be normalized. Post-write verification uses the same typed graph and checks new, already-present, and preserved assignments.

The participant result and error String variables are mandatory when participant mappings exist. Both are cleared at action start; failure detail is set before the failed outlet.

Fields are written and verified before participant writes because a security-team participant can alter subsequent authorization. All participant metadata and copy plans are nevertheless preflighted first to avoid preventable partial updates. A participant write failure reports that verified field writes may already have succeeded.

Field mapping model

Each mapping has a destination and either a source key or expression:

interface FieldMapEntry {
  destination: string;
  source?: string;
  transform?: "trim" | "uppercase" | "lowercase" | "dateOnly" | "nullIfEmpty";
  expression?: string;
}

Expression mode wins when both source and expression are present.

Route classification

Implemented in route-classifier.ts.

Key conventions (shared by destinations AND source refs):

Key shapeRouteValid as destinationValid as source
fieldNameAttributeyesyes
attr:fieldNameAttributeyesyes
core:title (and other writable core fields)Core work-item fieldyesyes
core:userTitleRaw user-set title exposed by GETno — public PUT silently ignores ityes
aspect:aspectName.path.to.valueAspect data pathyesyes
var:variableNameWorkflow context variable (v1.9)no — bucketByRoute rejects it with a pointer to the Update Variable actionyes

Unknown prefixes are treated as literal attribute keys, not errors.

Source-side routing (v1.9)

Prefixed keys work on the SOURCE side of single-source rows as well as inside expressions. buildEffectiveMapMultiSource resolves them with honest found-semantics per category (attribute/core/variable: hasOwnProperty; aspect: path walk), so strict mode still fails genuinely missing sources.

Pre-v1.9 gap (fixed): a single-source row with source: "core:title"
exactly what the Mapping Studio's CORE FIELDS drag group produces —
resolved only against the /attributes bag and was ALWAYS "missing" at
runtime. The legacy buildEffectiveMap (no SourceData) retains the old
behaviour; the template now routes through the multi-source builder
whenever collectSourceRefs finds any prefixed or variable ref.

var:name sources resolve from the workflow execution context (ctx[name]) — no HTTP. The template pre-scans the fieldMap with collectSourceRefs() to build the variables bag and to decide whether the extra source-workItem GET (core/aspect refs) is needed.

Core field whitelist

Only these core: fields are accepted:

  • title
  • titleIsUserProvided
  • userTitle
  • description
  • categoryId
  • reference
  • referenceIsUserProvided
  • externalReference
  • priorityId

When core:title is mapped and core:titleIsUserProvided is not explicitly supplied, the mapper injects titleIsUserProvided: true.

When core:reference is mapped and core:referenceIsUserProvided is not explicitly supplied, the mapper injects referenceIsUserProvided: true.

Aspect write behaviour

Aspect writes are handled as path patches:

  1. Classify destinations such as aspect:case-overview.summary.
  2. Read the existing destination work item for every core/aspect write.
  3. Clone existing aspectData.
  4. Apply patches using setNested.
  5. Round-trip the existing workItem and aspectData envelopes, overlay any

explicit writable core: fields, and PUT the complete save model. On playwithclio, an aspect-only body returns HTTP 400. userTitle is rejected before writing because the public update API silently ignores it even in a complete envelope.

Utilities live in aspect-merge.ts:

  • getNested
  • setNested
  • deepMerge
  • applyPatches

Arrays are replaced, not concatenated or element-merged. undefined is treated as no-op in deep merge; null explicitly clears.

Mapper flow

Implemented in mapper.ts.

High-level flow:

  1. Build explicit pairs from fieldMap.
  2. If autoMap is enabled, append same-name attribute pairs for source attributes not ignored and not already covered by explicit single-source entries.
  3. Resolve source values.
  4. Apply transforms.
  5. Partition into:
  • toWrite
  • skipped
  • missing
  • warnings
  1. Bucket toWrite by route:
  • attributes
  • coreFields
  • aspectPatches
  • unroutable

Strict mode fails before writing if any single-source mapping is missing. Lenient mode skips missing single-source mappings.

Expression mappings do not fail just because an internal field reference resolves empty. Parse, arity, or unknown-function errors are treated as missing-source failures.

Expression language

Implemented in expression-evaluator.ts and expression-functions.ts.

The evaluator is a small recursive-descent parser and does not use eval or Function.

Supported atoms:

  • String literals: "text"
  • Integer literals: 123, -1
  • Field references: {firstName}, {core:title}, {aspect:case-overview.summary}, {var:enquirySummary}
  • Function calls

Infix + concat operator (v1.9)

{firstName} + " " + {lastName} is valid anywhere an expression is expected (top level and inside call arguments). It desugars to concat(...) at PARSE time, so the evaluator, analyzeForRefs, and the runtime need no operator awareness.

Why it exists: the v1.8 Studio's drop-on-"+" flow generated {a} + {b}
expressions that the grammar could not parse — every such saved config
failed at runtime (strict mode) or wrote nothing (lenient). Adding the
operator heals those configs retroactively. + is PURE concatenation
(no separator); the Studio inserts explicit " " literals when it
combines sources by drag-and-drop.

Built-in functions:

  • concat(...)
  • uppercase(value)
  • lowercase(value)
  • trim(value)
  • default(...)
  • format-date(value, format?)
  • substring(value, start, end?)
  • replace(value, find, replacement)

Expression source references support the same prefixes as destinations:

ReferenceReads from
{firstName}Source attributes
{attr:firstName}Source attributes
{core:title}Source work item core field
{aspect:case-overview.summary}Source aspect data
{var:enquirySummary}Workflow context variable (ctx["enquirySummary"]) — v1.9

The template analyses the fieldMap before runtime reads (collectSourceRefs). It only fetches the source work item when a core: or aspect: ref appears (expression OR prefixed single-source row); var: refs are read straight from the workflow context with no HTTP.

Designer

The designer consists of:

  • factory.ts — creates Knockout observables, validation, outlets, and selector models
  • designer.ts — designer widget behaviour and blade integration
  • templates/CopyFieldsBetweenWorkTypesDesigner.html — node properties UI
  • work-type-picker-component.ts — custom work-type picker component
  • Mapping Studio integration via Alt.Widgets.VW.MappingStudio

Designer features include:

  • Source and destination work-item id variable selectors
  • Source and destination work-type pickers
  • Visual field picking (source, destination, and paired) via the shared runtime helper
  • Manual mapping table
  • Unified Mapping Studio launch for fields and participant roles
  • Strict/lenient mode controls
  • Auto-map and ignored-prefix configuration
  • Participant role mapping with allowed-type and cardinality warnings
  • Explicit incompatible-assignment and multi-to-single overflow policies
  • Participant result and error workflow-variable selectors
  • Three outlets: success, partial, failed

The custom work-type picker loads /api/sharedoTypes/tree once per page and provides list search, tree browsing, abstract-type toggle, selected-state display, and keyboard navigation.

The action factory loads the two work-type role endpoints once, then passes those role-contract snapshots and the existing participant mappings into Mapping Studio. The Studio blocks save when a role is absent or the allowed type sets do not overlap. Partial overlap, multi-to-single selection, and security-team roles are prominent text-and-icon warnings. Its participant-aware close result replaces both mapping collections atomically in the designer while preserving the existing runtime schema.

Field picking via AltRuntime.v1.dataComposer.pick (v1.5)

The three "Browse … fields" methods in designer.ts route through the shared runtime helper instead of poking $ui.stacks directly:

  • browseSourceFields() / browseDestinationFields() — one pick() call each, scoped to the picked work type.
  • browseAllFieldsAndPair() — two pick() calls in sequence (source then destination), then

pairPicksByTrailingSegment auto-creates paired rows on trailing-key match.

Each call is AltRuntime.v1.dataComposer.pick({ graphSystemName: "sharedo", entityType, mode: "multi-select-scalar" }) and returns a Promise<PickResult>. The helper opens the SAME OOB Sharedo.Core.Case.DataGraph.GraphBrowser panel via the confirmed closing/cancelled callback bag, so behaviour is preserved. We consume result.selection[].path.

mode is multi-select-scalar (the OOB per-mode gate already restricts to scalar/root-level fields, which is what the copy-fields runtime can write), so no extra canSelect gate is supplied. If the runtime Global Include is absent, getDataComposer() returns null and the browse buttons are a defensive no-op — the author can still hand-type keys in the mapping table (the raw-path escape hatch).

openMappingStudio() opens a DIFFERENT blade (Alt.Widgets.VW.MappingStudio), not GraphBrowser, so it keeps its own $ui.stacks.openPanel call and does NOT route through the helper.

Composer-path → attribute-key extraction (grammar-aware)

composer-path.ts (composerPathToAttributeKey) derives the trailing attribute key from a composer path. The FULL dotted path (selection[].path) is preserved centrally through the designer flow; the trailing key is derived ONLY at the point a mapping row is written (single-side) or inside pairPicksByTrailingSegment (paired).

The parser is grammar-aware: a composer path is split on . at the top tier, then the LAST dot-part's !q?… (query) and !N (indexer) sub-nodes are stripped before taking the field name. This mirrors the platform grammar (FieldPath.cs splits on . only; !/? are sub-delimiters within a dot-part). For the documented classes without !-nodes (forms., aspects., parent.aspects.*, bare attribute, core scalars) the output is byte-for-byte identical to the previous split(".").pop() — locked by composer-path.characterisation.test.ts. The fix only ADDS correct handling for ordered-child query-node paths, e.g.:

Composer pathAttribute key
forms.myForm.firstNamefirstName
aspects.case-overview.summarysummary
phase.namename
children!q?path=matter&orderBy=createdDate!1.phase.namename
children!q?path=matter!1 (bare indexed node, no leaf)children

Validation

Factory-level validation requires:

  • Source work-item id variable
  • Destination work-item id variable

Mapping rows with empty destination are filtered out on save. Rows must have either:

  • destination + source, or
  • destination + expression

Logging and failure behaviour

The runtime logs:

  • Source and destination variable names and resolved ids
  • Mode, auto-map setting, mapping counts, ignored prefixes
  • Source attribute read count
  • Multi-source expression read activity
  • Mapping resolution counts
  • Structured-value warnings
  • Write buckets
  • Unroutable mappings
  • Skipped lenient mappings
  • Per-route persistence checks at Information level

(source → destination, persisted=true|false; no literal or resolved values)

  • Verification pass count or mismatch route names; copied values are never logged
  • Completion outlet and duration

Failure routes to the failed outlet when connected. This includes read/write API errors and persistence mismatches. If no failed outlet is connected, the template throws an error so the workflow engine can handle it.

Test coverage

Behaviour is covered by unit tests for:

  • Aspect merge utilities
  • Composer path extraction (plus a composer-path.characterisation.test.ts regression baseline that locks the

trailing-key output for the documented path classes so the grammar-aware fix cannot silently regress them)

  • Expression parser/evaluator
  • Expression functions
  • Mapper behaviour
  • Source/destination GraphBrowser pick pairing
  • Route classification
  • Persistence equality and route-level checks
  • Runtime outcome routing after matching, mismatched, failed, and lenient read-back

The pure logic is intentionally separated from Sharedo runtime globals for testability.

Known technical limitations

  • Auto-map only copies source attributes.
  • Single-source mapping mode only reads source attributes; source core/aspect values require expression mode.
  • Complex typed values are copied as returned by the source API and may require destination-specific verification.
  • Unknown destination prefixes are treated as literal attribute names.
  • Core field support depends on the platform public PUT contract.
  • Aspect writes preserve existing sibling data, but aspect handlers with strict validation can still reject a merged payload.

How it's built

Component idAltCopyFieldsBetweenWorkTypes
Kindvw-action
CategoriesAlterspective
ConfigurableYes — designer Alt.CopyFieldsBetweenWorkTypesDesigner
Foundry-backedNo — local-runtime / homegrown pattern
Version2.0.0-alpha.1
Statusdeployed
TierT2
Sourcesrc/vw-actions/copy-fields-between-work-types

Git is the single source of truth — the component lives at src/vw-actions/copy-fields-between-work-types. Deployment is to the Sharedo IDE; the manifest defines how the platform loads it.

User guide

User guide

When to use this action

Use Copy Fields Between Work Items when a workflow needs to copy selected data from one work item to another.

Common examples:

  • Copy intake fields into a newly created item.
  • Copy parent item details into a child item.
  • Copy values between forms on the same work item.
  • Set a destination title, reference, priority, or aspect field from source data.
  • Build a computed destination value from several source fields.

Before you start

Create or identify two workflow variables of type Work Item Identifier:

  1. A variable containing the source work-item id.

2.


How to use it

In the Visual Workflow editor, find Copy Fields Between Work Items in the toolbox under the Alterspective category and drag it onto a step. Configure its inputs/outlets in the node's designer panel.

For the authoritative configuration fields and behaviour, see the component's designer help panel and the source linked in the footer.