@@ -683,29 +683,28 @@ func nonDeterminismDetail(opts IngestDurableTaskEventOpts, expectedKind sqlcv1.V
683683}
684684
685685type GetOrCreateLogEntryOpt struct {
686- Kind sqlcv1.V1DurableEventLogKind
687- IdempotencyKey []byte
688- LegacyIdempotencyKey []byte
689- InputPayload []byte
690- ResultPayload []byte
691- NodeId int64
692- BranchId int64
693- IsSatisfied bool
694- UserMessage * string
695- WaitData string // JSON-encoded WaitData, empty string means no wait data
696- ChildTaskExternalId uuid.UUID
697- ShouldSkip bool
686+ Kind sqlcv1.V1DurableEventLogKind
687+ IdempotencyKey []byte
688+ InputPayload []byte
689+ ResultPayload []byte
690+ NodeId int64
691+ BranchId int64
692+ IsSatisfied bool
693+ UserMessage * string
694+ WaitData string // JSON-encoded WaitData, empty string means no wait data
695+ // IsDagStepTrigger skips the idempotency check against an existing entry: the engine orchestrates the DAG
696+ // itself from a pinned workflow version, so its replays are deterministic by construction, whereas the key
697+ // hashes the run's input bytes, which change with the engine version that serialised them.
698+ IsDagStepTrigger bool
699+ ChildTaskExternalId uuid.UUID
700+ ShouldSkip bool
698701 // IsTriggered creates the entry with triggered_at already set: the child's task rows exist
699702 // (an in-place replay), so there is no run left for the trigger path to spawn.
700703 IsTriggered bool
701704}
702705
703- func (o GetOrCreateLogEntryOpt ) idempotencyKeyMatches (existingKey []byte ) bool {
704- if bytes .Equal (o .IdempotencyKey , existingKey ) {
705- return true
706- }
707-
708- return len (o .LegacyIdempotencyKey ) > 0 && bytes .Equal (o .LegacyIdempotencyKey , existingKey )
706+ func (o GetOrCreateLogEntryOpt ) matchesExistingEntry (existing * sqlcv1.BulkGetDurableEventLogEntriesRow ) bool {
707+ return o .IsDagStepTrigger || bytes .Equal (o .IdempotencyKey , existing .IdempotencyKey )
709708}
710709
711710type GetOrCreateLogEntryOpts struct {
@@ -822,26 +821,14 @@ func satisfiedOrderPtr(v pgtype.Int8) *int64 {
822821}
823822
824823func (r * durableEventsRepository ) createIdempotencyKey (kind sqlcv1.V1DurableEventLogKind , triggerOpts * WorkflowNameTriggerOpts , waitForConditions []CreateExternalSignalConditionOpt ) ([]byte , error ) {
825- return hashIdempotencyKey (kind , triggerOpts , waitForConditions , true )
826- }
827-
828- func (r * durableEventsRepository ) createLegacyDagStepIdempotencyKey (triggerOpts * WorkflowNameTriggerOpts ) ([]byte , error ) {
829- if ! triggerOpts .IsDagStepTrigger {
830- return nil , nil
831- }
832-
833- return hashIdempotencyKey (sqlcv1 .V1DurableEventLogKindRUN , triggerOpts , nil , false )
834- }
835-
836- func hashIdempotencyKey (kind sqlcv1.V1DurableEventLogKind , triggerOpts * WorkflowNameTriggerOpts , waitForConditions []CreateExternalSignalConditionOpt , includeDagStepIdentity bool ) ([]byte , error ) {
837824 // note: can't use additional metadata here because it's not stable, since we store trace information in it w/ the otel instrumentors
838825 dataToHash := []byte (kind )
839826
840827 if triggerOpts != nil {
841828 dataToHash = append (dataToHash , triggerOpts .Data ... )
842829 dataToHash = append (dataToHash , []byte (triggerOpts .WorkflowName )... )
843830
844- if includeDagStepIdentity && triggerOpts .IsDagStepTrigger {
831+ if triggerOpts .IsDagStepTrigger {
845832 if triggerOpts .TargetActionId != nil {
846833 dataToHash = append (dataToHash , []byte (* triggerOpts .TargetActionId )... )
847834 }
@@ -1129,7 +1116,7 @@ func (r *durableEventsRepository) getOrCreateEventLogEntriesForTasks(
11291116 state .newEntryByKey [key ] = o
11301117 continue
11311118 }
1132- if ! o .idempotencyKeyMatches ( e . IdempotencyKey ) {
1119+ if ! o .matchesExistingEntry ( e ) {
11331120 nonDeterminismErr = & NonDeterminismError {
11341121 BranchId : o .BranchId ,
11351122 NodeId : o .NodeId ,
@@ -1288,7 +1275,7 @@ func (r *durableEventsRepository) getOrCreateEventLogEntriesForTasks(
12881275 return nil , nil , nil , fmt .Errorf ("expected to find log entry for skipped child task external id %s" , o .ChildTaskExternalId )
12891276 }
12901277
1291- if len (o .IdempotencyKey ) > 0 && ! o .idempotencyKeyMatches ( e . IdempotencyKey ) {
1278+ if len (o .IdempotencyKey ) > 0 && ! o .matchesExistingEntry ( e ) {
12921279 return nil , nil , nil , & NonDeterminismError {
12931280 BranchId : e .BranchID ,
12941281 NodeId : e .NodeID ,
@@ -2112,31 +2099,25 @@ func (r *durableEventsRepository) planDurableEventLogAppend(
21122099 // only index-based dedupe is validated against the existing entry's
21132100 // idempotency key: an explicit child_key intentionally reuses the
21142101 // cached child even when the inputs differ
2115- var idempotencyKey , legacyIdempotencyKey []byte
2102+ var idempotencyKey []byte
21162103 if triggerOpts .ChildKey == nil {
21172104 key , keyErr := r .createIdempotencyKey (sqlcv1 .V1DurableEventLogKindRUN , triggerOpts , nil )
21182105 if keyErr != nil {
21192106 return nil , fmt .Errorf ("failed to create idempotency key: %w" , keyErr ), nil
21202107 }
21212108 idempotencyKey = key
2122-
2123- legacyKey , legacyKeyErr := r .createLegacyDagStepIdempotencyKey (triggerOpts )
2124- if legacyKeyErr != nil {
2125- return nil , fmt .Errorf ("failed to create legacy idempotency key: %w" , legacyKeyErr ), nil
2126- }
2127- legacyIdempotencyKey = legacyKey
21282109 }
21292110
21302111 if _ , exists := childExternalIdToTriggerOpts [triggerOpts .ExternalId ]; ! exists {
21312112 childExternalIdToTriggerOpts [triggerOpts .ExternalId ] = triggerOpts
21322113 }
21332114
21342115 innerOpts [i ] = GetOrCreateLogEntryOpt {
2135- Kind : sqlcv1 .V1DurableEventLogKindRUN ,
2136- ChildTaskExternalId : triggerOpts .ExternalId ,
2137- IdempotencyKey : idempotencyKey ,
2138- LegacyIdempotencyKey : legacyIdempotencyKey ,
2139- ShouldSkip : true ,
2116+ Kind : sqlcv1 .V1DurableEventLogKindRUN ,
2117+ ChildTaskExternalId : triggerOpts .ExternalId ,
2118+ IdempotencyKey : idempotencyKey ,
2119+ IsDagStepTrigger : triggerOpts . IsDagStepTrigger ,
2120+ ShouldSkip : true ,
21402121 }
21412122 continue
21422123 }
@@ -2159,29 +2140,24 @@ func (r *durableEventsRepository) planDurableEventLogAppend(
21592140 return nil , fmt .Errorf ("failed to create idempotency key: %w" , keyErr ), nil
21602141 }
21612142
2162- legacyIdempotencyKey , legacyKeyErr := r .createLegacyDagStepIdempotencyKey (triggerOpts )
2163- if legacyKeyErr != nil {
2164- return nil , fmt .Errorf ("failed to create legacy idempotency key: %w" , legacyKeyErr ), nil
2165- }
2166-
21672143 // A child that is being cancelled or skipped never runs, so no completion event
21682144 // will arrive for it (trigger.go creates it directly in a terminal state,
21692145 // bypassing the match pipeline): its entry is terminal at creation regardless of
21702146 // whether this is a first run or a replay.
21712147 isSatisfied := triggerOpts .IsCancelled || triggerOpts .IsSkipped
21722148
21732149 innerOpts [i ] = GetOrCreateLogEntryOpt {
2174- Kind : sqlcv1 .V1DurableEventLogKindRUN ,
2175- NodeId : nodeId ,
2176- BranchId : branchId ,
2177- ChildTaskExternalId : triggerOpts .ExternalId ,
2178- IdempotencyKey : idempotencyKey ,
2179- LegacyIdempotencyKey : legacyIdempotencyKey ,
2180- InputPayload : inputPayload ,
2181- WaitData : marshalWaitData ( waitDataFromTriggerOpt ( triggerOpts )) ,
2182- UserMessage : triggerOpts .UserMessage ,
2183- IsSatisfied : isSatisfied ,
2184- IsTriggered : childrenToReplay [triggerOpts .ExternalId ],
2150+ Kind : sqlcv1 .V1DurableEventLogKindRUN ,
2151+ NodeId : nodeId ,
2152+ BranchId : branchId ,
2153+ ChildTaskExternalId : triggerOpts .ExternalId ,
2154+ IdempotencyKey : idempotencyKey ,
2155+ InputPayload : inputPayload ,
2156+ WaitData : marshalWaitData ( waitDataFromTriggerOpt ( triggerOpts )) ,
2157+ UserMessage : triggerOpts . UserMessage ,
2158+ IsDagStepTrigger : triggerOpts .IsDagStepTrigger ,
2159+ IsSatisfied : isSatisfied ,
2160+ IsTriggered : childrenToReplay [triggerOpts .ExternalId ],
21852161 }
21862162 }
21872163
0 commit comments