@open-mercato/core 0.6.6-develop.6543.1.49fadb4c31 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_keys/api/keys/route.js +9 -23
  3. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  4. package/dist/modules/api_keys/cli.js +1 -2
  5. package/dist/modules/api_keys/cli.js.map +2 -2
  6. package/dist/modules/attachments/api/file/[id]/route.js +7 -9
  7. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  8. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
  9. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  10. package/dist/modules/attachments/api/route.js +17 -21
  11. package/dist/modules/attachments/api/route.js.map +2 -2
  12. package/dist/modules/auth/api/users/route.js +2 -6
  13. package/dist/modules/auth/api/users/route.js.map +2 -2
  14. package/dist/modules/auth/cli.js +2 -3
  15. package/dist/modules/auth/cli.js.map +2 -2
  16. package/dist/modules/configs/lib/upgrade-actions.js +0 -22
  17. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  18. package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
  19. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  20. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
  21. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  22. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +7 -11
  23. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  24. package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
  25. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  26. package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
  27. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  28. package/dist/modules/dashboards/cli.js +3 -4
  29. package/dist/modules/dashboards/cli.js.map +2 -2
  30. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
  31. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  32. package/dist/modules/dictionaries/commands/entries.js +14 -7
  33. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  34. package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
  35. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  36. package/dist/modules/dictionaries/commands/factory.js +3 -9
  37. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  38. package/dist/modules/dictionaries/fields/dictionary.js +41 -56
  39. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  40. package/dist/modules/directory/utils/organizationScope.js +3 -10
  41. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  42. package/dist/modules/entities/api/records.js +4 -5
  43. package/dist/modules/entities/api/records.js.map +2 -2
  44. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
  45. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  46. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  47. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  48. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
  49. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  50. package/dist/modules/entities/cli.js +1 -2
  51. package/dist/modules/entities/cli.js.map +2 -2
  52. package/dist/modules/query_index/api/status.js +8 -2
  53. package/dist/modules/query_index/api/status.js.map +2 -2
  54. package/dist/modules/sales/commands/documents.js +0 -44
  55. package/dist/modules/sales/commands/documents.js.map +2 -2
  56. package/dist/modules/sales/commands/returns.js +27 -2
  57. package/dist/modules/sales/commands/returns.js.map +2 -2
  58. package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
  59. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  60. package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
  61. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  62. package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
  63. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  64. package/dist/modules/sales/components/documents/SalesDocumentForm.js +0 -44
  65. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  66. package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
  67. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  68. package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
  69. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  70. package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
  71. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  72. package/dist/modules/workflows/lib/activity-executor.js +1 -1
  73. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  74. package/dist/modules/workflows/lib/find-definition.js +18 -2
  75. package/dist/modules/workflows/lib/find-definition.js.map +2 -2
  76. package/dist/modules/workflows/lib/signal-handler.js +3 -2
  77. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  78. package/dist/modules/workflows/lib/step-handler.js +4 -8
  79. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  80. package/dist/modules/workflows/lib/task-handler.js +3 -5
  81. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  82. package/dist/modules/workflows/lib/timer-handler.js +2 -1
  83. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  84. package/dist/modules/workflows/lib/transition-handler.js +5 -13
  85. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  86. package/dist/modules/workflows/lib/workflow-executor.js +11 -9
  87. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  88. package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
  89. package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
  90. package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
  91. package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
  92. package/dist/modules/workflows/workflows.js +2 -0
  93. package/dist/modules/workflows/workflows.js.map +2 -2
  94. package/package.json +8 -9
  95. package/src/modules/api_keys/api/keys/route.ts +9 -27
  96. package/src/modules/api_keys/cli.ts +3 -2
  97. package/src/modules/attachments/api/file/[id]/route.ts +7 -11
  98. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
  99. package/src/modules/attachments/api/route.ts +21 -26
  100. package/src/modules/auth/api/users/route.ts +2 -11
  101. package/src/modules/auth/cli.ts +2 -3
  102. package/src/modules/configs/i18n/de.json +1 -5
  103. package/src/modules/configs/i18n/en.json +1 -5
  104. package/src/modules/configs/i18n/es.json +1 -5
  105. package/src/modules/configs/i18n/pl.json +1 -5
  106. package/src/modules/configs/lib/upgrade-actions.ts +0 -24
  107. package/src/modules/customer_accounts/AGENTS.md +6 -8
  108. package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
  109. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
  110. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +7 -13
  111. package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
  112. package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
  113. package/src/modules/dashboards/cli.ts +9 -4
  114. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
  115. package/src/modules/dictionaries/commands/entries.ts +16 -7
  116. package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
  117. package/src/modules/dictionaries/commands/factory.ts +2 -8
  118. package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
  119. package/src/modules/dictionaries/i18n/de.json +0 -2
  120. package/src/modules/dictionaries/i18n/en.json +0 -2
  121. package/src/modules/dictionaries/i18n/es.json +0 -2
  122. package/src/modules/dictionaries/i18n/pl.json +0 -2
  123. package/src/modules/directory/utils/organizationScope.ts +2 -36
  124. package/src/modules/entities/api/records.ts +7 -5
  125. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
  126. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
  127. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
  128. package/src/modules/entities/cli.ts +1 -2
  129. package/src/modules/entities/i18n/de.json +0 -34
  130. package/src/modules/entities/i18n/en.json +0 -34
  131. package/src/modules/entities/i18n/es.json +0 -34
  132. package/src/modules/entities/i18n/pl.json +0 -34
  133. package/src/modules/query_index/api/status.ts +8 -2
  134. package/src/modules/sales/commands/documents.ts +0 -65
  135. package/src/modules/sales/commands/returns.ts +32 -2
  136. package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
  137. package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
  138. package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
  139. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +0 -46
  140. package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
  141. package/src/modules/sales/i18n/de.json +0 -4
  142. package/src/modules/sales/i18n/en.json +0 -4
  143. package/src/modules/sales/i18n/es.json +0 -4
  144. package/src/modules/sales/i18n/pl.json +0 -4
  145. package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
  146. package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
  147. package/src/modules/workflows/lib/activity-executor.ts +22 -2
  148. package/src/modules/workflows/lib/find-definition.ts +53 -1
  149. package/src/modules/workflows/lib/signal-handler.ts +5 -4
  150. package/src/modules/workflows/lib/step-handler.ts +4 -8
  151. package/src/modules/workflows/lib/task-handler.ts +2 -4
  152. package/src/modules/workflows/lib/timer-handler.ts +3 -2
  153. package/src/modules/workflows/lib/transition-handler.ts +5 -13
  154. package/src/modules/workflows/lib/workflow-executor.ts +11 -8
  155. package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
  156. package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
  157. package/src/modules/workflows/workflows.ts +2 -0
  158. package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
  159. package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
  160. package/dist/modules/attachments/lib/requestScope.js +0 -10
  161. package/dist/modules/attachments/lib/requestScope.js.map +0 -7
  162. package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
  163. package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
  164. package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
  165. package/src/modules/attachments/lib/requestScope.ts +0 -27
  166. package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
@@ -929,8 +929,28 @@ export async function executeCallApi(
929
929
  // 3. Import the one-time API key helper
930
930
  const { withOnetimeApiKey } = await import('../../api_keys/services/apiKeyService')
931
931
 
932
- // 4. Get EntityManager from container (for correct type)
933
- const apiKeyEm = container.resolve<PostgreSqlEntityManager>('em')
932
+ // 4. Create the one-time API key on an EntityManager that is fully detached
933
+ // from the surrounding request/transaction context.
934
+ //
935
+ // CALL_API runs inside `workflowExecutor.executeWorkflow()`, which wraps
936
+ // the whole execution in `em.transactional(...)`. The request EM is forked
937
+ // with `useContext: true`, so while that transaction is open, EVERY
938
+ // operation on the container's `em` — including this API key's
939
+ // persist/flush — is transparently redirected to the uncommitted
940
+ // transaction fork (MikroORM `getContext()` → `TransactionContext`). The
941
+ // key would therefore stay invisible until the transaction commits, but
942
+ // the commit cannot happen until this activity returns. The outbound
943
+ // self-authenticated `fetch` below opens a SEPARATE DB connection that
944
+ // cannot see the uncommitted row, so the internal API responds `401` and
945
+ // the activity fails (issue #4202).
946
+ //
947
+ // Forking with `useContext: false` (matching the query_index/webhooks
948
+ // isolated-EM convention) gives the key its own pooled connection with
949
+ // autocommit, so it is committed and visible to the internal request
950
+ // immediately.
951
+ const apiKeyEm = container
952
+ .resolve<PostgreSqlEntityManager>('em')
953
+ .fork({ clear: true, freshEventManager: true, useContext: false }) as PostgreSqlEntityManager
934
954
 
935
955
  // 5. Resolve the roles that the one-time API key will inherit.
936
956
  //
@@ -80,9 +80,17 @@ export async function findWorkflowDefinition(
80
80
  // so disabled code workflows aren't silently executable.
81
81
  if (version === undefined && !codeDef.enabled) return null
82
82
 
83
+ return buildVirtualCodeDefinition(codeDef, tenantId, organizationId)
84
+ }
85
+
86
+ function buildVirtualCodeDefinition(
87
+ codeDef: NonNullable<ReturnType<typeof getCodeWorkflow>>,
88
+ tenantId: string,
89
+ organizationId: string,
90
+ ): WorkflowDefinition {
83
91
  // Construct a virtual WorkflowDefinition object (not persisted)
84
92
  const virtual = new WorkflowDefinition()
85
- virtual.id = codeWorkflowUuid(workflowId)
93
+ virtual.id = codeWorkflowUuid(codeDef.workflowId)
86
94
  virtual.workflowId = codeDef.workflowId
87
95
  virtual.workflowName = codeDef.workflowName
88
96
  virtual.description = codeDef.description ?? null
@@ -98,3 +106,47 @@ export async function findWorkflowDefinition(
98
106
 
99
107
  return virtual
100
108
  }
109
+
110
+ /**
111
+ * Resolve a virtual code definition for an already-started instance whose
112
+ * `definitionId` does not exist in the database.
113
+ *
114
+ * Instances started from a virtual code definition persist the deterministic
115
+ * `codeWorkflowUuid(workflowId)` as their `definitionId`. Runtime handlers
116
+ * (step/transition/signal/timer/task) load definitions by that id, so without
117
+ * this fallback an unpersisted code workflow starts but can never advance.
118
+ *
119
+ * The UUID equality gate ensures the fallback never substitutes the code
120
+ * definition for a hard-deleted persisted row: those rows have their own
121
+ * random UUIDs that can never match the deterministic code UUID.
122
+ */
123
+ export function resolveCodeDefinitionForInstance(instance: {
124
+ definitionId: string
125
+ workflowId: string
126
+ tenantId: string
127
+ organizationId: string
128
+ }): WorkflowDefinition | null {
129
+ const codeDef = getCodeWorkflow(instance.workflowId)
130
+ if (!codeDef) return null
131
+ if (codeWorkflowUuid(instance.workflowId) !== instance.definitionId) return null
132
+ return buildVirtualCodeDefinition(codeDef, instance.tenantId, instance.organizationId)
133
+ }
134
+
135
+ /**
136
+ * Find the definition backing a workflow instance: database row by id first,
137
+ * falling back to the in-memory code registry for instances started from an
138
+ * unpersisted code definition.
139
+ */
140
+ export async function findDefinitionForInstance(
141
+ em: EntityManager,
142
+ instance: {
143
+ definitionId: string
144
+ workflowId: string
145
+ tenantId: string
146
+ organizationId: string
147
+ },
148
+ ): Promise<WorkflowDefinition | null> {
149
+ const dbDef = await em.findOne(WorkflowDefinition, { id: instance.definitionId })
150
+ if (dbDef) return dbDef
151
+ return resolveCodeDefinitionForInstance(instance)
152
+ }
@@ -13,6 +13,7 @@ import type * as eventLoggerModule from './event-logger'
13
13
  import type * as stepHandlerModule from './step-handler'
14
14
  import type * as transitionHandlerModule from './transition-handler'
15
15
  import type * as workflowExecutorModule from './workflow-executor'
16
+ import { resolveCodeDefinitionForInstance } from './find-definition'
16
17
  import { createLogger } from '@open-mercato/shared/lib/logger'
17
18
 
18
19
  const logger = createLogger('workflows')
@@ -95,13 +96,13 @@ export async function sendSignal(
95
96
  // Branch-scoped signal: a FORKED instance routes the signal to the branch
96
97
  // paused at a matching WAIT_FOR_SIGNAL step.
97
98
  if (instance.status === 'FORKED') {
98
- const branchDefinition = await findOneWithDecryption(
99
+ const branchDefinition = (await findOneWithDecryption(
99
100
  em as PostgreSqlEntityManager,
100
101
  WorkflowDefinition,
101
102
  { id: instance.definitionId, tenantId: instance.tenantId, organizationId: instance.organizationId, deletedAt: null },
102
103
  undefined,
103
104
  { tenantId: instance.tenantId, organizationId: instance.organizationId },
104
- )
105
+ )) ?? resolveCodeDefinitionForInstance(instance)
105
106
  if (!branchDefinition) {
106
107
  throw new SignalError('Workflow definition not found', 'DEFINITION_NOT_FOUND', { definitionId: instance.definitionId })
107
108
  }
@@ -173,7 +174,7 @@ export async function sendSignal(
173
174
  }
174
175
 
175
176
  // Load workflow definition with tenant/org scope to check current step
176
- const definition = await findOneWithDecryption(
177
+ const definition = (await findOneWithDecryption(
177
178
  em as PostgreSqlEntityManager,
178
179
  WorkflowDefinition,
179
180
  {
@@ -184,7 +185,7 @@ export async function sendSignal(
184
185
  },
185
186
  undefined,
186
187
  { tenantId: instance.tenantId, organizationId: instance.organizationId },
187
- )
188
+ )) ?? resolveCodeDefinitionForInstance(instance)
188
189
  if (!definition) {
189
190
  throw new SignalError(
190
191
  'Workflow definition not found',
@@ -13,7 +13,6 @@ import { EntityManager } from '@mikro-orm/core'
13
13
  import {
14
14
  WorkflowInstance,
15
15
  WorkflowBranchInstance,
16
- WorkflowDefinition,
17
16
  StepInstance,
18
17
  UserTask,
19
18
  WorkflowEvent,
@@ -22,6 +21,7 @@ import {
22
21
  } from '../data/entities'
23
22
  import { parseDuration } from './duration'
24
23
  import { logWorkflowEvent } from './event-logger'
24
+ import { findDefinitionForInstance } from './find-definition'
25
25
  import { createLogger } from '@open-mercato/shared/lib/logger'
26
26
 
27
27
  const logger = createLogger('workflows')
@@ -76,9 +76,7 @@ export async function enterStep(
76
76
  branch?: WorkflowBranchInstance | null
77
77
  ): Promise<StepInstance> {
78
78
  // Load workflow definition to get step details
79
- const definition = await em.findOne(WorkflowDefinition, {
80
- id: instance.definitionId,
81
- })
79
+ const definition = await findDefinitionForInstance(em, instance)
82
80
 
83
81
  if (!definition) {
84
82
  throw new StepExecutionError(
@@ -213,9 +211,7 @@ export async function executeStep(
213
211
  const stepInstance = await enterStep(em, instance, stepId, context, branch)
214
212
 
215
213
  // Load workflow definition to get step configuration
216
- const definition = await em.findOne(WorkflowDefinition, {
217
- id: instance.definitionId,
218
- })
214
+ const definition = await findDefinitionForInstance(em, instance)
219
215
 
220
216
  if (!definition) {
221
217
  throw new StepExecutionError(
@@ -359,7 +355,7 @@ async function executeStepByType(
359
355
  { stepType, stepId: stepDef.stepId }
360
356
  )
361
357
  }
362
- const definition = await em.findOne(WorkflowDefinition, { id: instance.definitionId })
358
+ const definition = await findDefinitionForInstance(em, instance)
363
359
  if (!definition) {
364
360
  throw new StepExecutionError(
365
361
  `Workflow definition not found: ${instance.definitionId}`,
@@ -17,9 +17,9 @@ import {
17
17
  WorkflowInstance,
18
18
  WorkflowEvent,
19
19
  StepInstance,
20
- WorkflowDefinition,
21
20
  } from '../data/entities'
22
21
  import { executeWorkflow } from './workflow-executor'
22
+ import { findDefinitionForInstance } from './find-definition'
23
23
  import * as stepHandler from './step-handler'
24
24
  import * as transitionHandler from './transition-handler'
25
25
  import { createLogger } from '@open-mercato/shared/lib/logger'
@@ -191,9 +191,7 @@ export async function completeUserTask(
191
191
  const currentStepId = instance.currentStepId
192
192
 
193
193
  // Load workflow definition to find transitions
194
- const definition = await em.findOne(WorkflowDefinition, {
195
- id: instance.definitionId,
196
- })
194
+ const definition = await findDefinitionForInstance(em, instance)
197
195
 
198
196
  if (!definition) {
199
197
  throw new UserTaskError(
@@ -14,6 +14,7 @@ import type * as eventLoggerModule from './event-logger'
14
14
  import type * as stepHandlerModule from './step-handler'
15
15
  import type * as transitionHandlerModule from './transition-handler'
16
16
  import type * as workflowExecutorModule from './workflow-executor'
17
+ import { resolveCodeDefinitionForInstance } from './find-definition'
17
18
 
18
19
  export interface FireTimerOptions {
19
20
  instanceId: string
@@ -110,7 +111,7 @@ export async function fireTimer(
110
111
  )
111
112
  }
112
113
 
113
- const definition = await findOneWithDecryption(
114
+ const definition = (await findOneWithDecryption(
114
115
  em as PostgreSqlEntityManager,
115
116
  WorkflowDefinition,
116
117
  {
@@ -121,7 +122,7 @@ export async function fireTimer(
121
122
  },
122
123
  undefined,
123
124
  { tenantId: instance.tenantId, organizationId: instance.organizationId },
124
- )
125
+ )) ?? resolveCodeDefinitionForInstance(instance)
125
126
  if (!definition) {
126
127
  throw new TimerError(
127
128
  'Workflow definition not found',
@@ -15,7 +15,6 @@ import type { AwilixContainer } from 'awilix'
15
15
  import type { EventBus } from '@open-mercato/events'
16
16
  import {
17
17
  WorkflowInstance,
18
- WorkflowDefinition,
19
18
  WorkflowEvent,
20
19
  } from '../data/entities'
21
20
  import * as ruleEvaluator from '../../business_rules/lib/rule-evaluator'
@@ -23,6 +22,7 @@ import * as ruleEngine from '../../business_rules/lib/rule-engine'
23
22
  import * as activityExecutor from './activity-executor'
24
23
  import type { ActivityDefinition } from './activity-executor'
25
24
  import * as stepHandler from './step-handler'
25
+ import { findDefinitionForInstance } from './find-definition'
26
26
  import {
27
27
  type ExecutionToken,
28
28
  rootToken,
@@ -116,9 +116,7 @@ export async function evaluateTransition(
116
116
 
117
117
  try {
118
118
  // Load workflow definition
119
- const definition = await em.findOne(WorkflowDefinition, {
120
- id: instance.definitionId,
121
- })
119
+ const definition = await findDefinitionForInstance(em, instance)
122
120
 
123
121
  if (!definition) {
124
122
  return {
@@ -227,9 +225,7 @@ export async function findValidTransitions(
227
225
  ): Promise<TransitionEvaluationResult[]> {
228
226
  try {
229
227
  // Load workflow definition
230
- const definition = await em.findOne(WorkflowDefinition, {
231
- id: instance.definitionId,
232
- })
228
+ const definition = await findDefinitionForInstance(em, instance)
233
229
 
234
230
  if (!definition) {
235
231
  return []
@@ -752,9 +748,7 @@ async function evaluatePreConditions(
752
748
  ): Promise<ruleEngine.RuleEngineResult> {
753
749
  try {
754
750
  // Load workflow definition to get workflow ID
755
- const definition = await em.findOne(WorkflowDefinition, {
756
- id: instance.definitionId,
757
- })
751
+ const definition = await findDefinitionForInstance(em, instance)
758
752
 
759
753
  if (!definition) {
760
754
  return {
@@ -881,9 +875,7 @@ async function evaluatePostConditions(
881
875
  ): Promise<ruleEngine.RuleEngineResult> {
882
876
  try {
883
877
  // Load workflow definition to get workflow ID
884
- const definition = await em.findOne(WorkflowDefinition, {
885
- id: instance.definitionId,
886
- })
878
+ const definition = await findDefinitionForInstance(em, instance)
887
879
 
888
880
  if (!definition) {
889
881
  return {
@@ -19,7 +19,7 @@ import {
19
19
  type WorkflowInstanceStatus,
20
20
  } from '../data/entities'
21
21
  import { compensateWorkflow } from './compensation-handler'
22
- import { findWorkflowDefinition } from './find-definition'
22
+ import { findWorkflowDefinition, findDefinitionForInstance } from './find-definition'
23
23
  import { createLogger } from '@open-mercato/shared/lib/logger'
24
24
 
25
25
  const logger = createLogger('workflows')
@@ -278,9 +278,7 @@ export async function executeWorkflow(
278
278
  )
279
279
  }
280
280
 
281
- const definition = await trx.findOne(WorkflowDefinition, {
282
- id: instance.definitionId,
283
- })
281
+ const definition = await findDefinitionForInstance(trx, instance)
284
282
 
285
283
  if (!definition) {
286
284
  throw new WorkflowExecutionError(
@@ -614,7 +612,7 @@ export async function completeWorkflow(
614
612
 
615
613
  // Trigger compensation if workflow failed and has compensatable activities (Phase 8.2)
616
614
  if (status === 'FAILED') {
617
- const definition = await em.findOne(WorkflowDefinition, { id: instance.definitionId })
615
+ const definition = await findDefinitionForInstance(em, instance)
618
616
 
619
617
  if (definition && checkIfCompensationNeeded(definition)) {
620
618
  try {
@@ -836,9 +834,14 @@ export async function resumeWorkflowAfterActivities(
836
834
  fromStepId: instance.currentStepId,
837
835
  })
838
836
 
839
- const definition = await trx.findOneOrFail(WorkflowDefinition, {
840
- id: instance.definitionId,
841
- })
837
+ const definition = await findDefinitionForInstance(trx, instance)
838
+ if (!definition) {
839
+ throw new WorkflowExecutionError(
840
+ `Workflow definition not found: ${instance.definitionId}`,
841
+ 'DEFINITION_NOT_FOUND',
842
+ { definitionId: instance.definitionId }
843
+ )
844
+ }
842
845
 
843
846
  const step = definition.definition.steps.find(s => s.stepId === pendingTransition.toStepId)
844
847
 
@@ -0,0 +1,114 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ const LEGACY_WORKFLOW_ID = 'workflows.checkout-demo'
4
+ const LEGACY_WORKFLOW_NAME = 'Checkout with Payment Webhook'
5
+ const LEGACY_ACTIVITY_ID = 'reserve_inventory'
6
+ const LEGACY_ACTIVITY_URL = '{{env.INVENTORY_SERVICE_URL}}/api/inventory/reserve'
7
+ const TRANSITIONS_REPLACED_BY_CODE_DEFINITION = [
8
+ 'cart_to_customer_info',
9
+ 'customer_info_to_payment',
10
+ 'confirmation_to_order',
11
+ ] as const
12
+
13
+ /**
14
+ * SQL fragment (for the UPDATE `where` clause) that matches ONLY the exact
15
+ * legacy checkout seed: workflow id, name, version, and the presence of the
16
+ * obsolete `reserve_inventory` `CALL_WEBHOOK` activity with its exact env-based
17
+ * URL. The match is intentionally strict so the repair never touches a row that
18
+ * is not the known-broken seed. A tenant whose persisted row diverges from this
19
+ * fingerprint (renamed, re-versioned, or a different webhook URL) is left
20
+ * untouched — the trade-off is safety over coverage for an unknown variant.
21
+ */
22
+ export function legacyCheckoutPredicate(): string {
23
+ return `
24
+ "wf"."workflow_id" = '${LEGACY_WORKFLOW_ID}'
25
+ and "wf"."workflow_name" = '${LEGACY_WORKFLOW_NAME}'
26
+ and "wf"."version" = 1
27
+ and exists (
28
+ select 1
29
+ from jsonb_array_elements(coalesce("wf"."definition"->'transitions', '[]'::jsonb)) as "transition"("value")
30
+ cross join lateral jsonb_array_elements(coalesce("transition"."value"->'activities', '[]'::jsonb)) as "activity"("value")
31
+ where "activity"."value"->>'activityId' = '${LEGACY_ACTIVITY_ID}'
32
+ and "activity"."value"->>'activityType' = 'CALL_WEBHOOK'
33
+ and "activity"."value"#>>'{config,url}' = '${LEGACY_ACTIVITY_URL}'
34
+ )
35
+ `.trim()
36
+ }
37
+
38
+ /**
39
+ * The exact UPDATE the migration runs. Exported so the DB-level regression test
40
+ * (`__integration__/TC-WF-031`) executes the identical SQL against Postgres,
41
+ * making the jsonb transformation itself — not just its rendered string —
42
+ * the thing under test.
43
+ *
44
+ * It rebuilds the `transitions` array in original order, dropping the
45
+ * `activities` key from exactly the three transitions the maintained code
46
+ * definition made side-effect-free. All other keys (e.g. `preConditions`) and
47
+ * all other transitions — including `confirmation_to_end`, whose remaining
48
+ * activities are internal (`CALL_API`/`SEND_EMAIL`/`EMIT_EVENT`) and never trip
49
+ * the outbound URL guard — are preserved verbatim.
50
+ */
51
+ export function buildLegacyCheckoutRepairSql(): string {
52
+ const strippedTransitionList = TRANSITIONS_REPLACED_BY_CODE_DEFINITION
53
+ .map((id) => `'${id}'`)
54
+ .join(', ')
55
+ return `
56
+ update "workflow_definitions" as "wf"
57
+ set "definition" = jsonb_set(
58
+ "wf"."definition",
59
+ '{transitions}',
60
+ coalesce(
61
+ (
62
+ select jsonb_agg(
63
+ case
64
+ when "transition"."value"->>'transitionId' in (${strippedTransitionList})
65
+ then "transition"."value" - 'activities'
66
+ else "transition"."value"
67
+ end
68
+ order by "transition"."ordinality"
69
+ )
70
+ from jsonb_array_elements(
71
+ coalesce("wf"."definition"->'transitions', '[]'::jsonb)
72
+ ) with ordinality as "transition"("value", "ordinality")
73
+ ),
74
+ '[]'::jsonb
75
+ )
76
+ ),
77
+ "updated_at" = current_timestamp
78
+ where "wf"."code_workflow_id" is null
79
+ and "wf"."deleted_at" is null
80
+ and ${legacyCheckoutPredicate()};
81
+ `.trim()
82
+ }
83
+
84
+ /**
85
+ * Repairs the former seeded checkout demo after it was renamed to the same ID
86
+ * as the code-defined workflow. The DB-first lookup shadows the current code
87
+ * definition, including its obsolete external inventory webhook.
88
+ *
89
+ * The persisted row must remain active because historical and in-flight
90
+ * workflow instances reference it by definition_id — a random UUID the
91
+ * code-registry fallback in find-definition.ts deliberately never substitutes.
92
+ * The migration removes the activity arrays from the three transitions that
93
+ * the maintained code definition intentionally made side-effect-free,
94
+ * preserving the row and all instance references while keeping the demo
95
+ * self-contained. (Fresh installs never had this row; they execute the virtual
96
+ * code definition directly via findDefinitionForInstance.)
97
+ *
98
+ * `confirmation_to_end` is deliberately NOT rebuilt: unlike the three stripped
99
+ * transitions it still holds activities in the maintained code definition, and
100
+ * its legacy-only extra (`emit_order_completed`, an internal `EMIT_EVENT`) is
101
+ * harmless — it emits no external request, so leaving it costs nothing and
102
+ * avoids fragile per-activity surgery that could re-introduce a failure.
103
+ */
104
+ export class Migration20260715120000 extends Migration {
105
+ override async up(): Promise<void> {
106
+ this.addSql(buildLegacyCheckoutRepairSql())
107
+ }
108
+
109
+ override async down(): Promise<void> {
110
+ // Forward-only data repair. The removed demo-only activity payloads cannot
111
+ // be reconstructed safely, and restoring obsolete external webhooks would
112
+ // re-open issue #4179.
113
+ }
114
+ }
@@ -0,0 +1,57 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ const CODE_WORKFLOW_ID = 'workflows.checkout-demo'
4
+
5
+ /**
6
+ * Retires the persisted checkout-demo seed rows so runtime resolves the
7
+ * maintained CODE definition instead (issue #4211).
8
+ *
9
+ * Background: `workflows.checkout-demo` is now code-defined
10
+ * (`packages/core/src/modules/workflows/workflows.ts`), and only the code
11
+ * definition forwards the cart's line items + shipping/tax adjustments to
12
+ * `POST /api/sales/orders`. But existing tenants still carry a persisted
13
+ * `workflow_definitions` row from the old seed — originally seeded under ids
14
+ * like `checkout_simple_v1` / names like `Simple Checkout Flow` and later
15
+ * `Checkout with Payment Webhook`, then renamed to `workflows.checkout-demo`
16
+ * by Migration20260428102318. Its `create_order` body never sent `lines`, and
17
+ * because `findWorkflowDefinition` resolves the DB row first, that stale row
18
+ * SHADOWS the code definition — so orders come out with zero line items and
19
+ * $0.00 totals. Patching the persisted body is unreliable: the seed's name and
20
+ * body shape vary across historical versions, so a fingerprint on either misses
21
+ * whole cohorts of tenants (the defect QA hit).
22
+ *
23
+ * Instead, soft-delete the seeded rows. This is body/name-agnostic, so it fixes
24
+ * every historical seed variant:
25
+ * - New instances: `findWorkflowDefinition` filters `deleted_at IS NULL`, so
26
+ * the retired row is skipped and the code definition is used (correct body).
27
+ * - In-flight instances: `findDefinitionForInstance` loads the definition by
28
+ * `id` with no soft-delete filter (WorkflowDefinition has no global filter),
29
+ * so instances that reference the row keep resolving and advancing.
30
+ *
31
+ * Scope is limited to `code_workflow_id IS NULL` rows — user customizations of
32
+ * the code definition set `code_workflow_id` and MUST NOT be retired. The
33
+ * `deleted_at IS NULL` guard makes the migration idempotent.
34
+ */
35
+ export function buildRetireSeededCheckoutDemoSql(): string {
36
+ return `
37
+ update "workflow_definitions" as "wf"
38
+ set "deleted_at" = current_timestamp,
39
+ "updated_at" = current_timestamp
40
+ where "wf"."workflow_id" = '${CODE_WORKFLOW_ID}'
41
+ and "wf"."code_workflow_id" is null
42
+ and "wf"."deleted_at" is null;
43
+ `.trim()
44
+ }
45
+
46
+ export class Migration20260716120000 extends Migration {
47
+ override async up(): Promise<void> {
48
+ this.addSql(buildRetireSeededCheckoutDemoSql())
49
+ }
50
+
51
+ override async down(): Promise<void> {
52
+ // Forward-only. Restoring `deleted_at = null` would re-shadow the maintained
53
+ // code definition with the stale seed body and re-introduce zero-line orders
54
+ // (#4211). The code definition supersedes the seed, so there is nothing to
55
+ // restore.
56
+ }
57
+ }
@@ -239,6 +239,8 @@ const checkoutDemo = defineWorkflow({
239
239
  customerEntityId: '{{context.customer.id}}',
240
240
  currencyCode: '{{context.cart.currency}}',
241
241
  placedAt: '{{now}}',
242
+ lines: '{{context.cart.orderLines}}',
243
+ adjustments: '{{context.cart.orderAdjustments}}',
242
244
  grandTotalGrossAmount: '{{context.cart.total}}',
243
245
  },
244
246
  validateTenantMatch: true,
@@ -1,110 +0,0 @@
1
- import { createLogger } from "@open-mercato/shared/lib/logger";
2
- import { Attachment } from "../data/entities.js";
3
- const logger = createLogger("attachments").child({ component: "reconcileOrganization" });
4
- const LIBRARY_ENTITY_ID = "attachments:library";
5
- const DEFAULT_BATCH_SIZE = 500;
6
- function readParentOrganizationId(record) {
7
- const raw = record["organization_id"] ?? record["organizationId"];
8
- if (typeof raw === "string" && raw.trim().length) return raw.trim();
9
- return null;
10
- }
11
- function ensureBucket(report, entityId) {
12
- const existing = report.byEntity[entityId];
13
- if (existing) return existing;
14
- const bucket = { scanned: 0, updated: 0, unresolved: 0 };
15
- report.byEntity[entityId] = bucket;
16
- return bucket;
17
- }
18
- async function reconcileAttachmentOrganizations(opts) {
19
- const { em, queryEngine, tenantId } = opts;
20
- const batchSize = opts.batchSize && opts.batchSize > 0 ? opts.batchSize : DEFAULT_BATCH_SIZE;
21
- const report = {
22
- scanned: 0,
23
- updated: 0,
24
- unresolved: 0,
25
- skippedVirtual: 0,
26
- byEntity: {}
27
- };
28
- const rows = await em.getConnection().execute(
29
- "select id, entity_id, record_id, organization_id from attachments where tenant_id = ?",
30
- [tenantId]
31
- );
32
- report.scanned = rows.length;
33
- if (!rows.length) return report;
34
- const groups = /* @__PURE__ */ new Map();
35
- for (const row of rows) {
36
- if (!row.id || !row.entity_id || !row.record_id) continue;
37
- const list = groups.get(row.entity_id) ?? [];
38
- list.push(row);
39
- groups.set(row.entity_id, list);
40
- }
41
- const pendingUpdates = [];
42
- for (const [entityId, group] of groups) {
43
- const bucket = ensureBucket(report, entityId);
44
- bucket.scanned += group.length;
45
- if (entityId === LIBRARY_ENTITY_ID) {
46
- report.skippedVirtual += group.length;
47
- continue;
48
- }
49
- const recordIds = Array.from(
50
- new Set(group.map((row) => row.record_id).filter((value) => !!value))
51
- );
52
- const parentOrgByRecordId = /* @__PURE__ */ new Map();
53
- let resolvable = true;
54
- for (let index = 0; index < recordIds.length; index += batchSize) {
55
- const chunk = recordIds.slice(index, index + batchSize);
56
- try {
57
- const result = await queryEngine.query(entityId, {
58
- fields: ["id", "organization_id"],
59
- filters: { id: chunk.length === 1 ? { $eq: chunk[0] } : { $in: chunk } },
60
- tenantId,
61
- withDeleted: true,
62
- page: { pageSize: Math.max(chunk.length, 1) }
63
- });
64
- for (const item of result.items ?? []) {
65
- const record = item;
66
- const recordId = record.id != null ? String(record.id) : null;
67
- if (!recordId) continue;
68
- parentOrgByRecordId.set(recordId, readParentOrganizationId(record));
69
- }
70
- } catch (error) {
71
- resolvable = false;
72
- logger.warn("org reconcile: cannot resolve parent organization for entity", {
73
- entityId,
74
- error
75
- });
76
- break;
77
- }
78
- }
79
- if (!resolvable) {
80
- bucket.unresolved += group.length;
81
- report.unresolved += group.length;
82
- continue;
83
- }
84
- for (const row of group) {
85
- const target = row.record_id ? parentOrgByRecordId.get(row.record_id) : null;
86
- if (!target) {
87
- bucket.unresolved += 1;
88
- report.unresolved += 1;
89
- continue;
90
- }
91
- if ((row.organization_id ?? null) === target) continue;
92
- pendingUpdates.push({ id: row.id, organizationId: target });
93
- bucket.updated += 1;
94
- report.updated += 1;
95
- }
96
- }
97
- for (let index = 0; index < pendingUpdates.length; index += batchSize) {
98
- const chunk = pendingUpdates.slice(index, index + batchSize);
99
- for (const update of chunk) {
100
- const reference = em.getReference(Attachment, update.id);
101
- reference.organizationId = update.organizationId;
102
- }
103
- await em.flush();
104
- }
105
- return report;
106
- }
107
- export {
108
- reconcileAttachmentOrganizations
109
- };
110
- //# sourceMappingURL=reconcileOrganization.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/attachments/lib/reconcileOrganization.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { Attachment } from '../data/entities'\n\nconst logger = createLogger('attachments').child({ component: 'reconcileOrganization' })\n\n/**\n * Virtual entity id for library attachments \u2014 these are not attached to a\n * parent record that owns an organization, so there is nothing to reconcile\n * against. They are counted as skipped rather than unresolved.\n */\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nconst DEFAULT_BATCH_SIZE = 500\n\nexport type AttachmentOrgReconcileEntityStat = {\n scanned: number\n updated: number\n unresolved: number\n}\n\nexport type AttachmentOrgReconcileReport = {\n scanned: number\n updated: number\n unresolved: number\n skippedVirtual: number\n byEntity: Record<string, AttachmentOrgReconcileEntityStat>\n}\n\ntype AttachmentScanRow = {\n id: string\n entity_id: string | null\n record_id: string | null\n organization_id: string | null\n}\n\nfunction readParentOrganizationId(record: Record<string, unknown>): string | null {\n const raw = record['organization_id'] ?? record['organizationId']\n if (typeof raw === 'string' && raw.trim().length) return raw.trim()\n return null\n}\n\nfunction ensureBucket(\n report: AttachmentOrgReconcileReport,\n entityId: string,\n): AttachmentOrgReconcileEntityStat {\n const existing = report.byEntity[entityId]\n if (existing) return existing\n const bucket: AttachmentOrgReconcileEntityStat = { scanned: 0, updated: 0, unresolved: 0 }\n report.byEntity[entityId] = bucket\n return bucket\n}\n\n/**\n * Reconcile the `organization_id` of existing attachments to the organization\n * of the record they are attached to.\n *\n * Background (#3765): before the upload route became selected-organization\n * aware, a multi-org admin who switched the header organization uploaded files\n * that were silently stored under their *home* organization instead of the\n * selected one (the organization of the parent record). Those rows are now\n * invisible to org-scoped reads and stay orphaned. This heals them forward.\n *\n * The parent record's organization is the ground truth: an attachment is not\n * distinguishable from a legitimately home-org one by looking at the attachment\n * alone. Parent organizations are resolved generically through the Query Engine\n * by `entityId` (works for both base and custom entities, tenant-scoped and\n * organization-agnostic so the parent's real org is returned regardless of\n * which org the caller sits in) \u2014 the same mechanism the attachments module\n * already uses to enrich assignment details.\n *\n * The reconciliation is:\n * - idempotent \u2014 rows already matching their parent are left untouched;\n * - conservative \u2014 when the parent org cannot be resolved (unregistered\n * entity id, hard-deleted parent, parent without an org column) the row is\n * counted as `unresolved` and left as-is rather than guessed at;\n * - tenant-scoped \u2014 it reconciles every attachment in the tenant across all\n * organizations (the whole point is moving rows between orgs).\n *\n * Writes go through the passed `EntityManager` (which the caller runs inside a\n * transaction) so the whole reconciliation commits atomically.\n */\nexport async function reconcileAttachmentOrganizations(opts: {\n em: EntityManager\n queryEngine: QueryEngine\n tenantId: string\n batchSize?: number\n}): Promise<AttachmentOrgReconcileReport> {\n const { em, queryEngine, tenantId } = opts\n const batchSize = opts.batchSize && opts.batchSize > 0 ? opts.batchSize : DEFAULT_BATCH_SIZE\n const report: AttachmentOrgReconcileReport = {\n scanned: 0,\n updated: 0,\n unresolved: 0,\n skippedVirtual: 0,\n byEntity: {},\n }\n\n const rows = (await em.getConnection().execute(\n 'select id, entity_id, record_id, organization_id from attachments where tenant_id = ?',\n [tenantId],\n )) as AttachmentScanRow[]\n report.scanned = rows.length\n if (!rows.length) return report\n\n const groups = new Map<string, AttachmentScanRow[]>()\n for (const row of rows) {\n if (!row.id || !row.entity_id || !row.record_id) continue\n const list = groups.get(row.entity_id) ?? []\n list.push(row)\n groups.set(row.entity_id, list)\n }\n\n const pendingUpdates: Array<{ id: string; organizationId: string }> = []\n\n for (const [entityId, group] of groups) {\n const bucket = ensureBucket(report, entityId)\n bucket.scanned += group.length\n\n if (entityId === LIBRARY_ENTITY_ID) {\n report.skippedVirtual += group.length\n continue\n }\n\n const recordIds = Array.from(\n new Set(group.map((row) => row.record_id).filter((value): value is string => !!value)),\n )\n const parentOrgByRecordId = new Map<string, string | null>()\n let resolvable = true\n\n for (let index = 0; index < recordIds.length; index += batchSize) {\n const chunk = recordIds.slice(index, index + batchSize)\n try {\n const result = await queryEngine.query(entityId as any, {\n fields: ['id', 'organization_id'],\n filters: { id: chunk.length === 1 ? { $eq: chunk[0] } : { $in: chunk } },\n tenantId,\n withDeleted: true,\n page: { pageSize: Math.max(chunk.length, 1) },\n })\n for (const item of result.items ?? []) {\n const record = item as Record<string, unknown>\n const recordId = record.id != null ? String(record.id) : null\n if (!recordId) continue\n parentOrgByRecordId.set(recordId, readParentOrganizationId(record))\n }\n } catch (error) {\n resolvable = false\n logger.warn('org reconcile: cannot resolve parent organization for entity', {\n entityId,\n error,\n })\n break\n }\n }\n\n if (!resolvable) {\n bucket.unresolved += group.length\n report.unresolved += group.length\n continue\n }\n\n for (const row of group) {\n const target = row.record_id ? parentOrgByRecordId.get(row.record_id) : null\n if (!target) {\n bucket.unresolved += 1\n report.unresolved += 1\n continue\n }\n if ((row.organization_id ?? null) === target) continue\n pendingUpdates.push({ id: row.id, organizationId: target })\n bucket.updated += 1\n report.updated += 1\n }\n }\n\n for (let index = 0; index < pendingUpdates.length; index += batchSize) {\n const chunk = pendingUpdates.slice(index, index + batchSize)\n for (const update of chunk) {\n const reference = em.getReference(Attachment, update.id)\n reference.organizationId = update.organizationId\n }\n await em.flush()\n }\n\n return report\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }