@@ -9,11 +9,13 @@ import (
99 historypb "go.temporal.io/api/history/v1"
1010 "go.temporal.io/api/serviceerror"
1111 workflowpb "go.temporal.io/api/workflow/v1"
12+ "go.temporal.io/server/api/historyservice/v1"
1213 "go.temporal.io/server/chasm"
1314 "go.temporal.io/server/chasm/lib/activity"
1415 "go.temporal.io/server/chasm/lib/callback"
1516 callbackspb "go.temporal.io/server/chasm/lib/callback/gen/callbackpb/v1"
1617 "go.temporal.io/server/chasm/lib/nexusoperation"
18+ "go.temporal.io/server/common/metrics"
1719 "go.temporal.io/server/service/history/historybuilder"
1820 "google.golang.org/protobuf/types/known/emptypb"
1921 "google.golang.org/protobuf/types/known/timestamppb"
@@ -176,11 +178,46 @@ func (w *Workflow) OnActivityTimedOut(ctx chasm.MutableContext, act *activity.Ac
176178}
177179
178180// OnActivityCanceled implements ActivityStore for workflow-embedded activities.
179- // No ActivityTaskCanceled history event is written on the forward path in this prototype.
180- func (w * Workflow ) OnActivityCanceled (ctx chasm.MutableContext , act * activity.Activity ) error {
181- activityID := act .GetActivityId ()
182- delete (w .Activities , activityID )
183- return w .ScheduleWorkflowTask ()
181+ // When needsStartedEvent is true (activity was started before being canceled), writes
182+ // ActivityTaskStarted + ActivityTaskCanceled history events; Apply() handles cleanup.
183+ // When needsStartedEvent is false (activity was never started), the caller is responsible
184+ // for writing the ActivityTaskCanceled event (via AddActivityTaskCanceledEventCHASM).
185+ func (w * Workflow ) OnActivityCanceled (ctx chasm.MutableContext , act * activity.Activity , needsStartedEvent bool ) error {
186+ if ! needsStartedEvent {
187+ // Not-started case: ActivityTaskCanceled is written by AddActivityTaskCanceledEventCHASM.
188+ // Just clean up the activity and schedule a WFT.
189+ activityID := act .GetActivityId ()
190+ delete (w .Activities , activityID )
191+ return w .ScheduleWorkflowTask ()
192+ }
193+
194+ scheduledEventID := act .GetScheduledEventId ()
195+ attempt := act .LastAttempt .Get (ctx )
196+ startedEvent , err := addAndApplyHistoryEvent [ActivityTaskStartedEventDefinition ](w , ctx , func (e * historypb.HistoryEvent ) {
197+ e .Attributes = & historypb.HistoryEvent_ActivityTaskStartedEventAttributes {
198+ ActivityTaskStartedEventAttributes : & historypb.ActivityTaskStartedEventAttributes {
199+ ScheduledEventId : scheduledEventID ,
200+ Attempt : attempt .GetCount (),
201+ RequestId : attempt .GetStartRequestId (),
202+ Identity : attempt .GetLastWorkerIdentity (),
203+ },
204+ }
205+ })
206+ if err != nil {
207+ return err
208+ }
209+ cancelDetails := act .Outcome .Get (ctx ).GetFailed ().GetFailure ().GetCanceledFailureInfo ().GetDetails ()
210+ _ , err = addAndApplyHistoryEvent [ActivityTaskCanceledEventDefinition ](w , ctx , func (e * historypb.HistoryEvent ) {
211+ e .Attributes = & historypb.HistoryEvent_ActivityTaskCanceledEventAttributes {
212+ ActivityTaskCanceledEventAttributes : & historypb.ActivityTaskCanceledEventAttributes {
213+ ScheduledEventId : scheduledEventID ,
214+ StartedEventId : startedEvent .GetEventId (),
215+ Details : cancelDetails ,
216+ Identity : attempt .GetLastWorkerIdentity (),
217+ },
218+ }
219+ })
220+ return err
184221}
185222
186223// OnActivityTerminated implements ActivityStore for workflow-embedded activities.
@@ -243,6 +280,54 @@ func (w *Workflow) AddCompletionCallbacks(
243280 return nil
244281}
245282
283+ // RegisterScheduledActivity applies TransitionScheduled to act and adds it to the workflow's
284+ // Activities map. This is the single entry point for scheduling embedded activities so that
285+ // the transition trigger stays inside the workflow package rather than in the caller.
286+ func (w * Workflow ) RegisterScheduledActivity (
287+ ctx chasm.MutableContext ,
288+ activityID string ,
289+ act * activity.Activity ,
290+ ) error {
291+ if err := activity .TransitionScheduled .Apply (act , ctx , nil ); err != nil {
292+ return err
293+ }
294+ w .AddEmbeddedActivity (ctx , activityID , act )
295+ return nil
296+ }
297+
298+ // RequestCancelEmbeddedActivity finds the embedded activity with scheduledEventID, applies the
299+ // cancel transition, and returns (wasNotStarted, found, error). found=false means no CHASM
300+ // activity has that scheduledEventID; the caller should fall back to the legacy path.
301+ func (w * Workflow ) RequestCancelEmbeddedActivity (
302+ ctx chasm.MutableContext ,
303+ scheduledEventID int64 ,
304+ identity string ,
305+ ) (wasNotStarted bool , found bool , err error ) {
306+ act := w .FindActivityByScheduledEventID (ctx , scheduledEventID )
307+ if act == nil {
308+ return false , false , nil
309+ }
310+ wasNotStarted , err = act .RequestCancelFromWorkflowTask (ctx , identity )
311+ return wasNotStarted , true , err
312+ }
313+
314+ // CompleteEmbeddedActivityByID finds the activity by activityID, applies TransitionCompleted,
315+ // and returns (true, nil) on success. Returns (false, nil) if no CHASM activity with that ID
316+ // exists; the caller should fall back to the legacy path.
317+ func (w * Workflow ) CompleteEmbeddedActivityByID (
318+ ctx chasm.MutableContext ,
319+ activityID string ,
320+ req * historyservice.RespondActivityTaskCompletedRequest ,
321+ metricsHandler metrics.Handler ,
322+ ) (bool , error ) {
323+ actField , ok := w .Activities [activityID ]
324+ if ! ok {
325+ return false , nil
326+ }
327+ act := actField .Get (ctx )
328+ return true , activity .TransitionCompleted .Apply (act , ctx , activity .NewCompleteEvent (req , metricsHandler ))
329+ }
330+
246331// AddEmbeddedActivity adds a CHASM activity sub-component to the workflow, keyed by SDK-provided activity ID.
247332func (w * Workflow ) AddEmbeddedActivity (
248333 ctx chasm.MutableContext ,
0 commit comments