@pxp-nd/flow 1.1.29 → 1.1.31

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.
@@ -0,0 +1,566 @@
1
+ # BPM + Automation Platform — Design Proposal
2
+
3
+ **Status:** Draft
4
+ **Base:** Current Tempo Flows (`twf_*`, `EventHq`, SQS worker) — see [FLOWS.md](./FLOWS.md)
5
+ **Stack lean (v1):** NestJS + React + PostgreSQL + SQS/BullMQ
6
+
7
+ ---
8
+
9
+ ## 1. Summary — what we want to build
10
+
11
+ We want a **new process platform** that keeps everything that already works in Tempo Flows (visual automation, action catalog, event + queue, multi-tenant templates) and extends it into a real **BPM** product.
12
+
13
+ **In short:**
14
+
15
+ | Today (Flows) | Tomorrow (BPM + Automation) |
16
+ |---------------|-----------------------------|
17
+ | Event → delay → email/push/controller | Same automations, cleaner engine |
18
+ | Mostly linear graphs | Gateways (branch / parallel) with clear UI |
19
+ | Little human workflow | Forms, approvals, assignees, task inbox |
20
+ | No first-class documents | Require / upload / generate documents |
21
+ | Internal controllers mainly | HTTP / webhooks / external connectors |
22
+ | Soft versioning | Immutable process versions |
23
+ | Magic vendor `55` | Explicit **SYSTEM / GROUP / TENANT** catalog scopes |
24
+
25
+ **Product shape (same engine, two modes):**
26
+
27
+ 1. **Automation** — fire-and-forget graphs (billing alerts, COS push, funnels).
28
+ 2. **BPM** — long-running cases (form → approve → documents → notify → close).
29
+
30
+ **Builder UX (keep):** left = action palette · center = canvas · right = node form (`schema_json`).
31
+
32
+ **Migration:** strangler pattern — keep `twf_*` running; migrate process-by-process (e.g. Safe Families first); do not big-bang rewrite.
33
+
34
+ ---
35
+
36
+ ## 2. ER diagram (color legend)
37
+
38
+ | Color | Meaning |
39
+ |-------|---------|
40
+ | **Amber** | Exists today as `twf_*` → **renamed / evolved** |
41
+ | **Green** | **Completely new** (no direct `twf_*` equivalent) |
42
+
43
+ ```mermaid
44
+ flowchart TB
45
+ classDef renamed fill:#FFF3CD,stroke:#B8860B,color:#000000,stroke-width:2px
46
+ classDef newtbl fill:#D1E7DD,stroke:#0F5132,color:#000000,stroke-width:2px
47
+ classDef legend fill:#F8F9FA,stroke:#6C757D,color:#000000
48
+
49
+ subgraph LEGEND["Legend"]
50
+ L1["AMBER = existing twf_* → renamed/evolved"]
51
+ L2["GREEN = completely new"]
52
+ L1:::legend
53
+ L2:::legend
54
+ end
55
+
56
+ subgraph TENANCY["Tenancy"]
57
+ TG["bpm_tenant_group"]:::newtbl
58
+ TGM["bpm_tenant_group_member"]:::newtbl
59
+ AG["bpm_action_group"]:::newtbl
60
+ end
61
+
62
+ subgraph DESIGN["Design-time"]
63
+ AT["bpm_action_type<br/>← twf_action_type"]:::renamed
64
+ A["bpm_action<br/>← twf_action"]:::renamed
65
+ PD["bpm_process_definition<br/>← twf_flow"]:::renamed
66
+ PV["bpm_process_version<br/>← split from twf_flow"]:::renamed
67
+ N["bpm_node<br/>← twf_node"]:::renamed
68
+ E["bpm_edge<br/>← twf_node_connection"]:::renamed
69
+ FD["bpm_form_definition"]:::newtbl
70
+ FV["bpm_form_version"]:::newtbl
71
+ DT["bpm_document_type"]:::newtbl
72
+ AP["bpm_approval_policy"]:::newtbl
73
+ end
74
+
75
+ subgraph RUNTIME["Runtime"]
76
+ PI["bpm_process_instance<br/>← twf_flow_instance"]:::renamed
77
+ AI["bpm_activity_instance<br/>← twf_node_instance"]:::renamed
78
+ TK["bpm_token"]:::newtbl
79
+ T["bpm_task"]:::newtbl
80
+ TA["bpm_task_action"]:::newtbl
81
+ DOC["bpm_document"]:::newtbl
82
+ MSG["bpm_message<br/>← twf_event"]:::renamed
83
+ JOB["bpm_job"]:::newtbl
84
+ AUD["bpm_audit_log<br/>← twf_flow_log"]:::renamed
85
+ end
86
+
87
+ TG --> TGM
88
+ A --> AG
89
+ TG --> AG
90
+ AT --> A
91
+ A --> N
92
+ PD --> PV
93
+ PV --> N
94
+ PV --> E
95
+ FD --> FV
96
+ FV -.-> N
97
+ AP -.-> N
98
+ DT -.-> N
99
+ PV --> PI
100
+ PI --> AI
101
+ PI --> TK
102
+ N --> AI
103
+ AI --> T
104
+ T --> TA
105
+ PI --> DOC
106
+ DT --> DOC
107
+ MSG --> PI
108
+ PI --> JOB
109
+ PI --> AUD
110
+ ```
111
+
112
+ ```mermaid
113
+ erDiagram
114
+ bpm_tenant_group ||--o{ bpm_tenant_group_member : has
115
+ bpm_tenant_group ||--o{ bpm_action_group : scopes
116
+ bpm_action_type ||--o{ bpm_action : categorizes
117
+ bpm_action ||--o{ bpm_action_group : visible_in
118
+ bpm_action ||--o{ bpm_node : used_by
119
+ bpm_process_definition ||--o{ bpm_process_version : has
120
+ bpm_process_version ||--o{ bpm_node : contains
121
+ bpm_process_version ||--o{ bpm_edge : contains
122
+ bpm_form_definition ||--o{ bpm_form_version : has
123
+ bpm_form_version ||--o{ bpm_node : bound_to
124
+ bpm_approval_policy ||--o{ bpm_node : configures
125
+ bpm_document_type ||--o{ bpm_node : required_by
126
+ bpm_process_version ||--o{ bpm_process_instance : instantiates
127
+ bpm_process_instance ||--o{ bpm_activity_instance : has
128
+ bpm_process_instance ||--o{ bpm_token : active_at
129
+ bpm_node ||--o{ bpm_activity_instance : executes_as
130
+ bpm_activity_instance ||--o{ bpm_task : creates
131
+ bpm_task ||--o{ bpm_task_action : records
132
+ bpm_process_instance ||--o{ bpm_document : owns
133
+ bpm_document_type ||--o{ bpm_document : typed_as
134
+ bpm_message ||--o{ bpm_process_instance : starts_or_signals
135
+ bpm_process_instance ||--o{ bpm_job : schedules
136
+ bpm_process_instance ||--o{ bpm_audit_log : audited_by
137
+ ```
138
+
139
+ **Color checklist**
140
+
141
+ | Amber — renamed / evolved | Green — new |
142
+ |---------------------------|-------------|
143
+ | `bpm_action_type` ← `twf_action_type` | `bpm_tenant_group` |
144
+ | `bpm_action` ← `twf_action` | `bpm_tenant_group_member` |
145
+ | `bpm_process_definition` ← `twf_flow` | `bpm_action_group` |
146
+ | `bpm_process_version` ← split from `twf_flow` | `bpm_form_definition` / `bpm_form_version` |
147
+ | `bpm_node` ← `twf_node` | `bpm_document_type` |
148
+ | `bpm_edge` ← `twf_node_connection` | `bpm_approval_policy` |
149
+ | `bpm_process_instance` ← `twf_flow_instance` | `bpm_token` |
150
+ | `bpm_activity_instance` ← `twf_node_instance` | `bpm_task` / `bpm_task_action` |
151
+ | `bpm_message` ← `twf_event` | `bpm_document` |
152
+ | `bpm_audit_log` ← `twf_flow_log` | `bpm_job` |
153
+
154
+ > Tenant identity maps to today’s `vendor_id`. Optional bridge tables `twf_origin_name` / `twf_field_map` can remain for legacy view-based automations; BPM case data lives in `bpm_process_instance.variables`.
155
+
156
+ ---
157
+
158
+ ## 3. Data dictionary
159
+
160
+ Conventions used below:
161
+
162
+ - `tenant_id` = vendor id (unless noted).
163
+ - JSON examples are illustrative.
164
+ - `visibility_scope`: `SYSTEM` | `GROUP` | `TENANT`.
165
+
166
+ ---
167
+
168
+ ### 3.1 Tenancy
169
+
170
+ #### `bpm_tenant_group` *(new)*
171
+
172
+ Groups of tenants that share a catalog slice (e.g. business type).
173
+
174
+ | Column | Type | Description | Example |
175
+ |--------|------|-------------|---------|
176
+ | `id` | PK | Group id | `1` |
177
+ | `code` | varchar | Stable code | `SF` |
178
+ | `name` | varchar | Display name | `Safe Families` |
179
+ | `is_active` | bool | Soft enable | `true` |
180
+
181
+ #### `bpm_tenant_group_member` *(new)*
182
+
183
+ | Column | Type | Description | Example |
184
+ |--------|------|-------------|---------|
185
+ | `id` | PK | Row id | `10` |
186
+ | `tenant_id` | int | Vendor id | `674` |
187
+ | `tenant_group_id` | FK | Group | `1` (`SF`) |
188
+
189
+ #### `bpm_action_group` *(new)*
190
+
191
+ Links a GROUP-scoped action to one or more tenant groups.
192
+
193
+ | Column | Type | Description | Example |
194
+ |--------|------|-------------|---------|
195
+ | `action_id` | FK | Action | `111` |
196
+ | `tenant_group_id` | FK | Group that may use it | `1` (`SF`) |
197
+
198
+ ---
199
+
200
+ ### 3.2 Catalog (palette)
201
+
202
+ #### `bpm_action_type` *(← `twf_action_type`)*
203
+
204
+ Category in the left palette (Delay, Email, Controller, HTTP, Approval, …).
205
+
206
+ | Column | Type | Description | Example |
207
+ |--------|------|-------------|---------|
208
+ | `id` | PK | Type id | `28` |
209
+ | `code` | varchar | Stable type code | `CONTROLLER_EXECUTION` |
210
+ | `name` | varchar | Palette group label | `CONTROLLER EXECUTION` |
211
+ | `schema_json` | json | Shared UI schema for the type | `{ "timeout": { ... } }` |
212
+ | `is_start` | char(1) | Can be a start/trigger type | `N` |
213
+ | `is_delay` | char(1) | Timer-like behavior | `N` |
214
+ | `on_duplicate` | varchar | Hook when process is cloned | `duplicateEmailTemplate` |
215
+ | `is_active` | bool | Visible in palette | `true` |
216
+
217
+ #### `bpm_action` *(← `twf_action`)*
218
+
219
+ Reusable step/event definition (stable contract via `code`).
220
+
221
+ | Column | Type | Description | Example |
222
+ |--------|------|-------------|---------|
223
+ | `id` | PK | Action id | `111` |
224
+ | `action_type_id` | FK | Category | `28` |
225
+ | `code` | varchar | **Stable API key** (never hardcode numeric id in callers) | `SF SEND COS PUSH` |
226
+ | `name` | varchar | Builder label | `Send Push Notification to COS members` |
227
+ | `description` | text | Help text in palette | `Sends a push when a COS message is posted` |
228
+ | `schema_json` | json | Right-panel form for this action | `{ "subject": {...}, "message": {...} }` |
229
+ | `config_json_template` | json | Defaults / `{{ FIELD }}` placeholders | `{ "subject": "{{ FAMILY_NAME }}" }` |
230
+ | `controller_function` | varchar | Internal handler or connector key | `hq-nd/BusinessTypeSF/sendCircleOfSupportNotification` |
231
+ | `visibility_scope` | varchar | Who can use it | `GROUP` |
232
+ | `owner_tenant_id` | int null | Set when scope = `TENANT` | `null` |
233
+ | `is_enabled` | bool | Kill switch | `true` |
234
+ | `icon` | varchar | Palette icon | `mdi:bell-ring-outline` |
235
+
236
+ ---
237
+
238
+ ### 3.3 Process definition (design-time graph)
239
+
240
+ #### `bpm_process_definition` *(← `twf_flow` metadata)*
241
+
242
+ Logical process (many versions).
243
+
244
+ | Column | Type | Description | Example |
245
+ |--------|------|-------------|---------|
246
+ | `id` | PK | Definition id | `100` |
247
+ | `key` | varchar | Stable process key | `sf-new-cos-message` |
248
+ | `name` | varchar | Display name | `New COS Message` |
249
+ | `category` | varchar | `automation` or `bpm` | `automation` |
250
+ | `owner_tenant_id` | int null | Owning vendor; null for platform template | `674` |
251
+ | `visibility_scope` | varchar | Who may clone/subscribe | `GROUP` |
252
+ | `icon` | varchar | UI icon | `chatbubbles-outline` |
253
+ | `is_active` | bool | Soft delete | `true` |
254
+
255
+ #### `bpm_process_version` *(split from `twf_flow`)*
256
+
257
+ Immutable published graph. Running instances pin a version.
258
+
259
+ | Column | Type | Description | Example |
260
+ |--------|------|-------------|---------|
261
+ | `id` | PK | Version id | `45` |
262
+ | `definition_id` | FK | Parent definition | `100` |
263
+ | `version` | int | Monotonic version number | `3` |
264
+ | `status` | varchar | `draft` \| `published` \| `retired` | `published` |
265
+ | `published_at` | timestamp null | Publish time | `2026-07-15T12:00:00Z` |
266
+ | `graph_checksum` | varchar | Optional integrity hash | `sha256:…` |
267
+
268
+ #### `bpm_node` *(← `twf_node`)*
269
+
270
+ One element on the canvas.
271
+
272
+ | Column | Type | Description | Example |
273
+ |--------|------|-------------|---------|
274
+ | `id` | PK | Node id | `18200` |
275
+ | `version_id` | FK | Process version | `45` |
276
+ | `action_id` | FK null | Catalog action (null for pure gateways/start/end) | `111` |
277
+ | `element_type` | varchar | BPMN-ish type | `serviceTask` / `userTask` / `exclusiveGateway` / `startEvent` |
278
+ | `name` | varchar | Label on canvas | `Send Push Notification to COS members` |
279
+ | `is_init` | bool | Entry / trigger node | `false` |
280
+ | `config_json` | json | Values from right-panel form (`action_config_json` today) | `{ "subject": "{{ REGISTERED_BY_NAME }}", "message": "{{ MESSAGE }}" }` |
281
+ | `form_version_id` | FK null | Bound form (user tasks) | `12` |
282
+ | `approval_policy_id` | FK null | Approval rules | `3` |
283
+ | `document_type_id` | FK null | Required doc type | `null` |
284
+ | `position_x` / `position_y` | int | Canvas coordinates | `120`, `340` |
285
+
286
+ #### `bpm_edge` *(← `twf_node_connection`)*
287
+
288
+ Directed connection; may carry a condition (gateway branch).
289
+
290
+ | Column | Type | Description | Example |
291
+ |--------|------|-------------|---------|
292
+ | `id` | PK | Edge id | `900` |
293
+ | `version_id` | FK | Process version | `45` |
294
+ | `from_node_id` | FK null | Source; null = link into start | `18199` |
295
+ | `to_node_id` | FK | Target | `18200` |
296
+ | `name` | varchar null | Branch label | `approved` |
297
+ | `condition_expr` | varchar null | Expression for XOR/default | `variables.decision == "approved"` |
298
+ | `is_default` | bool | Default flow on gateway | `false` |
299
+
300
+ ---
301
+
302
+ ### 3.4 Forms, approvals, documents (design-time)
303
+
304
+ #### `bpm_form_definition` *(new)*
305
+
306
+ | Column | Type | Description | Example |
307
+ |--------|------|-------------|---------|
308
+ | `id` | PK | Form id | `5` |
309
+ | `key` | varchar | Stable key | `sf-need-review` |
310
+ | `name` | varchar | Display name | `Need review form` |
311
+ | `visibility_scope` | varchar | Catalog scope | `GROUP` |
312
+ | `owner_tenant_id` | int null | If `TENANT` | `null` |
313
+
314
+ #### `bpm_form_version` *(new)*
315
+
316
+ | Column | Type | Description | Example |
317
+ |--------|------|-------------|---------|
318
+ | `id` | PK | Form version id | `12` |
319
+ | `form_id` | FK | Parent form | `5` |
320
+ | `version` | int | Version number | `2` |
321
+ | `schema_json` | json | JSON Schema of fields | `{ "type":"object", "properties": { "notes": { "type":"string" } } }` |
322
+ | `ui_schema` | json | UI layout hints | `{ "notes": { "ui:widget":"textarea" } }` |
323
+ | `status` | varchar | `draft` \| `published` | `published` |
324
+
325
+ #### `bpm_approval_policy` *(new)*
326
+
327
+ | Column | Type | Description | Example |
328
+ |--------|------|-------------|---------|
329
+ | `id` | PK | Policy id | `3` |
330
+ | `code` | varchar | Stable code | `SF_AREA_ANY` |
331
+ | `policy_type` | varchar | `ANY` \| `ALL` \| `N_OF_M` | `ANY` |
332
+ | `quorum_n` | int null | Required when `N_OF_M` | `null` |
333
+ | `assignee_resolver` | json | How to resolve approvers | `{ "type":"expression", "resolver":"sfAreaApprovers", "args": { "familyAreaId": "{{ variables.familyAreaId }}" } }` |
334
+ | `outcomes` | json | Allowed outcomes | `["approved","rejected","changes_requested"]` |
335
+
336
+ #### `bpm_document_type` *(new)*
337
+
338
+ | Column | Type | Description | Example |
339
+ |--------|------|-------------|---------|
340
+ | `id` | PK | Doc type id | `7` |
341
+ | `code` | varchar | Stable code | `HOST_AGREEMENT` |
342
+ | `name` | varchar | Label | `Host agreement PDF` |
343
+ | `mime_allowlist` | json | Allowed MIME types | `["application/pdf"]` |
344
+ | `max_size_mb` | int | Upload limit | `10` |
345
+ | `visibility_scope` | varchar | Catalog scope | `GROUP` |
346
+
347
+ ---
348
+
349
+ ### 3.5 Runtime
350
+
351
+ #### `bpm_process_instance` *(← `twf_flow_instance`)*
352
+
353
+ One running (or completed) case.
354
+
355
+ | Column | Type | Description | Example |
356
+ |--------|------|-------------|---------|
357
+ | `id` | PK | Instance id | `5001` |
358
+ | `version_id` | FK | Pinned process version | `45` |
359
+ | `tenant_id` | int | Owning vendor | `674` |
360
+ | `business_key` | varchar | External correlation id | `cos-message:8891` |
361
+ | `status` | varchar | `running` \| `waiting` \| `completed` \| `failed` \| `cancelled` | `running` |
362
+ | `variables` | json | Case data (replaces heavy reliance on SQL views) | `{ "referralId": 321, "familyName": "The Smith Family", "familyInitials": "SM", "message": "Can someone cover Friday?" }` |
363
+ | `started_at` / `ended_at` | timestamp | Lifecycle | `2026-07-16T14:01:00Z` / `null` |
364
+
365
+ #### `bpm_activity_instance` *(← `twf_node_instance`)*
366
+
367
+ One execution of a node inside an instance.
368
+
369
+ | Column | Type | Description | Example |
370
+ |--------|------|-------------|---------|
371
+ | `id` | PK | Activity id | `80001` |
372
+ | `instance_id` | FK | Process instance | `5001` |
373
+ | `node_id` | FK | Definition node | `18200` |
374
+ | `status` | varchar | `pending` \| `active` \| `completed` \| `failed` \| `skipped` | `completed` |
375
+ | `input_json` | json | Snapshot of inputs | `{ "subject": "Jane Doe", "message": "…" }` |
376
+ | `output_json` | json | Snapshot of outputs | `{ "sent": 3, "skipped": 1 }` |
377
+ | `error_message` | text null | Failure detail | `null` |
378
+ | `started_at` / `completed_at` | timestamp | Timing | … |
379
+
380
+ #### `bpm_token` *(new)*
381
+
382
+ Active position(s) in the graph (required for parallel gateways).
383
+
384
+ | Column | Type | Description | Example |
385
+ |--------|------|-------------|---------|
386
+ | `id` | PK | Token id | `60` |
387
+ | `instance_id` | FK | Process instance | `5001` |
388
+ | `node_id` | FK | Current node | `18200` |
389
+ | `status` | varchar | `active` \| `consumed` | `active` |
390
+ | `parent_token_id` | FK null | For parallel forks | `null` |
391
+
392
+ #### `bpm_task` *(new)*
393
+
394
+ Human inbox item (form / approval).
395
+
396
+ | Column | Type | Description | Example |
397
+ |--------|------|-------------|---------|
398
+ | `id` | PK | Task id | `900` |
399
+ | `activity_id` | FK | Related activity | `80010` |
400
+ | `instance_id` | FK | Process instance (denormalized for queries) | `5001` |
401
+ | `tenant_id` | int | Tenant | `674` |
402
+ | `task_type` | varchar | `FORM` \| `APPROVAL` | `APPROVAL` |
403
+ | `title` | varchar | Inbox title | `Approve posted need #441` |
404
+ | `assignee` | varchar null | Claimed user id/email | `approver@sf.org` |
405
+ | `candidate_users` | json | Potential assignees | `["a@sf.org","b@sf.org"]` |
406
+ | `candidate_groups` | json | Role/group keys | `["SF_AREA_12"]` |
407
+ | `due_at` | timestamp null | SLA due | `2026-07-17T18:00:00Z` |
408
+ | `status` | varchar | `open` \| `completed` \| `cancelled` | `open` |
409
+ | `form_version_id` | FK null | Form to render | `12` |
410
+ | `payload_json` | json | Task-specific data | `{ "needId": 441, "kind": "post" }` |
411
+
412
+ #### `bpm_task_action` *(new)*
413
+
414
+ Audit of human actions on a task.
415
+
416
+ | Column | Type | Description | Example |
417
+ |--------|------|-------------|---------|
418
+ | `id` | PK | Action id | `1200` |
419
+ | `task_id` | FK | Task | `900` |
420
+ | `actor` | varchar | Who acted | `a@sf.org` |
421
+ | `action` | varchar | `approve` \| `reject` \| `comment` \| `reassign` \| `complete` | `approve` |
422
+ | `comment` | text null | Optional note | `Looks good` |
423
+ | `data_json` | json null | Submitted form fields | `{ "notes": "OK" }` |
424
+ | `created_at` | timestamp | When | `2026-07-16T15:00:00Z` |
425
+
426
+ #### `bpm_document` *(new)*
427
+
428
+ Uploaded or generated file for an instance.
429
+
430
+ | Column | Type | Description | Example |
431
+ |--------|------|-------------|---------|
432
+ | `id` | PK | Document id | `70` |
433
+ | `instance_id` | FK | Process instance | `5001` |
434
+ | `document_type_id` | FK | Type | `7` |
435
+ | `tenant_id` | int | Tenant | `674` |
436
+ | `file_name` | varchar | Original name | `host-agreement.pdf` |
437
+ | `s3_key` | varchar | Storage key | `bpm/674/5001/host-agreement.pdf` |
438
+ | `mime_type` | varchar | MIME | `application/pdf` |
439
+ | `status` | varchar | `required` \| `uploaded` \| `approved` \| `rejected` | `uploaded` |
440
+ | `uploaded_by` | varchar null | Actor | `coach@sf.org` |
441
+
442
+ #### `bpm_message` *(← `twf_event`)*
443
+
444
+ Inbound event / signal that starts or resumes a process.
445
+
446
+ | Column | Type | Description | Example |
447
+ |--------|------|-------------|---------|
448
+ | `id` | PK | Message id | `56953` |
449
+ | `action_code` | varchar | Trigger action code (or process key) | `SF NEW COS MESSAGE` |
450
+ | `tenant_id` | int null | Known tenant if resolved | `674` |
451
+ | `business_key` | varchar null | Correlation | `cos-message:8891` |
452
+ | `payload_json` | json | Event body / origin row snapshot | `{ "id": 8891, "referralId": 321, "familyName": "The Smith Family", "familyInitials": "SM" }` |
453
+ | `desc_job_name` | varchar | Caller context (like today) | `CircleOfSupport/createMessage` |
454
+ | `status` | varchar | `pending` \| `processed` \| `failed` | `processed` |
455
+ | `process_instance_id` | FK null | Instance created/signaled | `5001` |
456
+
457
+ #### `bpm_job` *(new)*
458
+
459
+ Async work: timers, HTTP retries, delayed continuations (worker queue rows).
460
+
461
+ | Column | Type | Description | Example |
462
+ |--------|------|-------------|---------|
463
+ | `id` | PK | Job id | `300` |
464
+ | `instance_id` | FK null | Related instance | `5001` |
465
+ | `activity_id` | FK null | Related activity | `80001` |
466
+ | `job_type` | varchar | `TIMER` \| `HTTP` \| `EMAIL` \| `PUSH` \| `CONTINUE` | `PUSH` |
467
+ | `run_at` | timestamp | When eligible | `2026-07-16T14:01:05Z` |
468
+ | `attempts` | int | Retry count | `1` |
469
+ | `max_attempts` | int | Cap | `4` |
470
+ | `payload_json` | json | Worker payload | `{ "actionCode": "SF SEND COS PUSH", "config": { ... } }` |
471
+ | `status` | varchar | `scheduled` \| `running` \| `done` \| `failed` | `done` |
472
+ | `last_error` | text null | Last failure | `null` |
473
+
474
+ #### `bpm_audit_log` *(← `twf_flow_log`)*
475
+
476
+ Immutable trail for support and compliance.
477
+
478
+ | Column | Type | Description | Example |
479
+ |--------|------|-------------|---------|
480
+ | `id` | PK | Log id | `10001` |
481
+ | `instance_id` | FK null | Process instance | `5001` |
482
+ | `tenant_id` | int null | Tenant | `674` |
483
+ | `event_type` | varchar | `INSTANCE_STARTED` \| `TASK_COMPLETED` \| `NODE_FAILED` \| … | `NODE_FAILED` |
484
+ | `message` | text | Human-readable summary | `Push connector timeout` |
485
+ | `details_json` | json | Structured detail / stack excerpt | `{ "activityId": 80001, "error": "ETIMEDOUT" }` |
486
+ | `created_at` | timestamp | When | `2026-07-16T14:02:00Z` |
487
+
488
+ ---
489
+
490
+ ## 4. Multi-tenancy (catalog visibility) — short
491
+
492
+ | Scope | Who sees the action/form/template |
493
+ |-------|-----------------------------------|
494
+ | `SYSTEM` | All tenants |
495
+ | `GROUP` | Tenants in linked `bpm_tenant_group` (via `bpm_action_group`) |
496
+ | `TENANT` | Only `owner_tenant_id` |
497
+
498
+ Builder palette = `SYSTEM ∪ GROUP(member) ∪ TENANT(owner)`.
499
+ **Always re-validate on publish and execute** (never trust UI alone).
500
+ Instance/task/document data is always hard-scoped by `tenant_id`.
501
+
502
+ Replace today’s “vendor 55 runs for everyone” with **platform/group templates + tenant clone or subscription**.
503
+
504
+ ---
505
+
506
+ ## Appendix A — Keep / improve from current Flows
507
+
508
+ **Keep:** stable action `code`, action catalog + `schema_json`, node graph, event + SQS worker, template duplicate, instance history, 3-pane builder.
509
+
510
+ **Improve:** human tasks, approvals, forms, documents, gateway UX, case variables, HTTP connectors, immutable versions, observability.
511
+
512
+ Details of the current engine: [FLOWS.md](./FLOWS.md).
513
+
514
+ ---
515
+
516
+ ## Appendix B — Target capabilities (MVP → later)
517
+
518
+ | Capability | Approach |
519
+ |------------|----------|
520
+ | Forms | `bpm_form_*` + User Task node |
521
+ | Approvals | `bpm_approval_policy` + `bpm_task` |
522
+ | Branching | `exclusiveGateway` / `parallelGateway` + `bpm_edge.condition_expr` + `bpm_token` |
523
+ | Documents | `bpm_document_type` + `bpm_document` + S3 |
524
+ | External APIs | Action types `HTTP_REQUEST`, `WEBHOOK_WAIT`, connectors |
525
+ | Automations | Same engine; category=`automation`; service tasks only |
526
+
527
+ ---
528
+
529
+ ## Appendix C — Stack & migration
530
+
531
+ | Layer | Choice |
532
+ |-------|--------|
533
+ | API + engine v1 | **NestJS** modular monolith |
534
+ | Workers | NestJS + SQS/BullMQ |
535
+ | UI | React (evolve Harmony builder + task inbox) |
536
+ | DB | PostgreSQL preferred |
537
+ | Later | Optional Go workers if metrics demand |
538
+
539
+ **Migration:** keep `twf_*` live → adapter from `EventHq.add` → migrate SF processes first → retire legacy write path when ready.
540
+
541
+ ---
542
+
543
+ ## Appendix D — Open decisions
544
+
545
+ 1. Expression language: JSONLogic vs CEL vs JMESPath
546
+ 2. Form tech: RJSF vs Formily vs custom
547
+ 3. Postgres vs MySQL for the new DB
548
+ 4. Inbox inside Harmony vs separate app
549
+ 5. BPMN XML export in v1 or JSON-only
550
+ 6. Tenant groups: reuse `business_type` vs dedicated packs
551
+ 7. Global automations: subscription (recommended) vs fan-out listener
552
+
553
+ ---
554
+
555
+ ## Appendix E — Example process (Safe Families)
556
+
557
+ ```text
558
+ Start (SF NEW COS MESSAGE / SF NEW POSTED NEED)
559
+ → Service Task: notify (push)
560
+ → [BPM] User/Approval Task (optional)
561
+ → Exclusive Gateway (approved / rejected)
562
+ → Service Task: decision notification
563
+ → End
564
+ ```
565
+
566
+ Same trigger model as today; richer graph when human steps are needed.